You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by sm...@apache.org on 2020/08/31 13:47:43 UTC
[knox] branch master updated: KNOX-2402 - Adding Gateway
performance testing (#365)
This is an automated email from the ASF dual-hosted git repository.
smolnar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new a48eff8 KNOX-2402 - Adding Gateway performance testing (#365)
a48eff8 is described below
commit a48eff8425c90db6b58b871053de8506550045fd
Author: Sandor Molnar <sm...@apache.org>
AuthorDate: Mon Aug 31 15:47:32 2020 +0200
KNOX-2402 - Adding Gateway performance testing (#365)
---
.../main/resources/build-tools/spotbugs-filter.xml | 4 +
gateway-performance-test/pom.xml | 126 +++++++++++++++
.../performance/test/ExecutorServiceUtils.java | 44 +++++
.../test/PerformanceTestConfiguration.java | 141 ++++++++++++++++
.../test/PerformanceTestLifeCyleListener.java | 24 +++
.../performance/test/PerformanceTestMessages.java | 79 +++++++++
.../performance/test/PerformanceTestRunner.java | 49 ++++++
.../performance/test/ResponseTimeCache.java | 45 ++++++
.../gateway/performance/test/UseCaseRunner.java | 30 ++++
.../test/knoxtoken/KnoxTokenAction.java | 23 +++
.../performance/test/knoxtoken/KnoxTokenCache.java | 41 +++++
.../test/knoxtoken/KnoxTokenUseCaseRunner.java | 79 +++++++++
.../test/knoxtoken/KnoxTokenWorkerThread.java | 178 +++++++++++++++++++++
.../test/reporting/AbstractReportEngine.java | 66 ++++++++
.../test/reporting/GatewayMetricsReporter.java | 142 ++++++++++++++++
.../test/reporting/JsonReportEngine.java | 44 +++++
.../performance/test/reporting/ReportEngine.java | 26 +++
.../test/reporting/YamlReportEngine.java | 47 ++++++
...che.knox.gateway.performance.test.UseCaseRunner | 19 +++
.../performance.test.configuration.properties | 35 ++++
.../resources/performanceTest-log4j.properties | 35 ++++
gateway-release/pom.xml | 4 +
.../security/impl/DefaultKeystoreService.java | 2 +-
.../token/TokenStateServiceStatistics.java | 91 +++++++++++
.../token/impl/AliasBasedTokenStateService.java | 52 ++++--
.../token/impl/DefaultTokenStateService.java | 55 +++++--
.../apache/knox/gateway/shell/AbstractRequest.java | 9 ++
.../token/TokenStateServiceStatisticsMBean.java | 31 ++++
pom.xml | 6 +
29 files changed, 1500 insertions(+), 27 deletions(-)
diff --git a/build-tools/src/main/resources/build-tools/spotbugs-filter.xml b/build-tools/src/main/resources/build-tools/spotbugs-filter.xml
index 4bd0c00..80057e5 100644
--- a/build-tools/src/main/resources/build-tools/spotbugs-filter.xml
+++ b/build-tools/src/main/resources/build-tools/spotbugs-filter.xml
@@ -76,4 +76,8 @@ limitations under the License.
<Bug pattern="NM_SAME_SIMPLE_NAME_AS_SUPERCLASS" />
</Match>
+ <Match>
+ <Class name="org.apache.knox.gateway.performance.test.PerformanceTestConfiguration" />
+ <Bug pattern="PATH_TRAVERSAL_IN" />
+ </Match>
</FindBugsFilter>
diff --git a/gateway-performance-test/pom.xml b/gateway-performance-test/pom.xml
new file mode 100644
index 0000000..d7dac8c
--- /dev/null
+++ b/gateway-performance-test/pom.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.knox</groupId>
+ <artifactId>gateway</artifactId>
+ <version>1.5.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>gateway-performance-test</artifactId>
+ <name>gateway-performance-test</name>
+ <description>A test framework to measure the Knox Gateway's performance</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.knox</groupId>
+ <artifactId>gateway-i18n</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.knox</groupId>
+ <artifactId>gateway-i18n-logging-log4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.knox</groupId>
+ <artifactId>gateway-shell</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.knox</groupId>
+ <artifactId>gateway-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.knox</groupId>
+ <artifactId>gateway-util-common</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-yaml</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-math3</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpcore</artifactId>
+ </dependency>
+ </dependencies>
+ <profiles>
+ <profile>
+ <id>gateway-performance-test</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>${exec-maven-plugin.version}</version>
+ <executions>
+ <execution>
+ <id>run_gateway_performance_test</id>
+ <phase>test</phase>
+ <goals>
+ <goal>java</goal>
+ </goals>
+ <configuration>
+ <mainClass>org.apache.knox.gateway.performance.test.PerformanceTestRunner</mainClass>
+ <arguments>
+ <argument>${basedir}/src/test/resources/performance.test.configuration.properties</argument>
+ </arguments>
+ <systemProperties>
+ <systemProperty>
+ <key>perf.test.report.generation.target.folder</key>
+ <value>${basedir}/target/testResults</value>
+ </systemProperty>
+ <systemProperty>
+ <key>log4j.configuration</key>
+ <value>file:${basedir}/src/test/resources/performanceTest-log4j.properties</value>
+ </systemProperty>
+ </systemProperties>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+</project>
\ No newline at end of file
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/ExecutorServiceUtils.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/ExecutorServiceUtils.java
new file mode 100644
index 0000000..6754a0f
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/ExecutorServiceUtils.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.knox.gateway.performance.test;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class ExecutorServiceUtils {
+
+ public static void shutdownAndAwaitTermination(ExecutorService executorService, long timeout, TimeUnit timeUnit) {
+ executorService.shutdown(); // Disable new tasks from being submitted
+ try {
+ // Wait a while for existing tasks to terminate
+ if (!executorService.awaitTermination(timeout, timeUnit)) {
+ executorService.shutdownNow(); // Cancel currently executing tasks
+ // Wait a while for tasks to respond to being cancelled
+ if (!executorService.awaitTermination(timeout, timeUnit)) {
+ System.err.println("Pool did not terminate");
+ }
+ }
+ } catch (InterruptedException ie) {
+ // (Re-)Cancel if current thread also interrupted
+ executorService.shutdownNow();
+ // Preserve interrupt status
+ Thread.currentThread().interrupt();
+ }
+ }
+
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestConfiguration.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestConfiguration.java
new file mode 100644
index 0000000..6149605
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestConfiguration.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.knox.gateway.performance.test;
+
+
+import static org.apache.knox.gateway.performance.test.knoxtoken.KnoxTokenUseCaseRunner.USE_CASE_NAME;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.commons.lang3.StringUtils;
+
+public class PerformanceTestConfiguration {
+
+ private static final String URL_PATH_SEPARATOR = "/";
+ private static final String PERF_TEST_PREFIX = "perf.test.";
+ private static final String GATEWAY_PREFIX = PERF_TEST_PREFIX + "gateway.";
+ private static final String PARAM_GATEWAY_URL_PROTOROL = GATEWAY_PREFIX + "url.protocol";
+ private static final String DEFAULT_GATEWAY_URL_PROTOCOL = "https";
+ private static final String PARAM_GATEWAY_URL_HOST = GATEWAY_PREFIX + "url.host";
+ private static final String DEFAULT_GATEWAY_URL_HOST = "localhost";
+ private static final String PARAM_GATEWAY_URL_PORT = GATEWAY_PREFIX + "url.port";
+ private static final String DEFAULT_GATEWAY_URL_PORT = "8443";
+ private static final String GATEWAY_URL_TEMPLATE = "%s://%s:%d";
+ private static final String PARAM_GATEWAY_JMX_PORT = GATEWAY_PREFIX + "jmx.port";
+ private static final String DEFAULT_GATEWAY_JMX_PORT = "8888";
+ private static final String GATEWAY_JMX_URL_TEMPLATE = "service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi";
+ private static final String PARAM_GATEWAY_PATH = GATEWAY_PREFIX + "path";
+ private static final String DEFAULT_GATEWAY_PATH = "gateway";
+ private static final String PARAM_GATEWAY_USER = GATEWAY_PREFIX + "user";
+ private static final String DEFAULT_GATEWAY_USER = "guest";
+ private static final String PARAM_GATEWAY_PW = GATEWAY_PREFIX + "pw";
+ private static final String DEFAULT_GATEWAY_PW = "guest-password";
+
+ private static final String REPORT_GENERATION_PREFIX = PERF_TEST_PREFIX + "report.generation.";
+ private static final String PARAM_REPORT_GENERATION_PERIOD = REPORT_GENERATION_PREFIX + "periodInSecs";
+ private static final String DEFAULT_REPORT_GENERATION_PERIOD = "60";
+ private static final String PARAM_REPORT_GENERATION_TARGET_FOLDER = REPORT_GENERATION_PREFIX + "target.folder";
+ private static final String PARAM_ENABLED_POSTFIX = ".enabled";
+ private static final String USE_CASE_PREFIX = PERF_TEST_PREFIX + "usecase.";
+ private static final String PARAM_USE_CASE_TOPOLOGY_POSTFIX = ".topology";
+
+ private final Properties configuration = new Properties();
+ private final Map<String, Map<String, String>> defaultUseCaseMap;
+
+ PerformanceTestConfiguration(String configFileLocation) throws IOException {
+ final Path configFilePath = Paths.get(configFileLocation);
+ try (Reader configFileReader = Files.newBufferedReader(configFilePath, StandardCharsets.UTF_8)) {
+ configuration.load(configFileReader);
+ }
+ final Map<String, String> knoxTokenDefaultTopologies = new HashMap<>();
+ knoxTokenDefaultTopologies.put("gateway", "sandbox");
+ knoxTokenDefaultTopologies.put("tokenbased", "tokenbased");
+ defaultUseCaseMap = new HashMap<>();
+ defaultUseCaseMap.put(USE_CASE_NAME, knoxTokenDefaultTopologies);
+ }
+
+ /* Gateway connection */
+
+ public String getGatewayUrl() {
+ final String protocol = configuration.getProperty(PARAM_GATEWAY_URL_PROTOROL, DEFAULT_GATEWAY_URL_PROTOCOL);
+ final String host = configuration.getProperty(PARAM_GATEWAY_URL_HOST, DEFAULT_GATEWAY_URL_HOST);
+ final int port = Integer.parseInt(configuration.getProperty(PARAM_GATEWAY_URL_PORT, DEFAULT_GATEWAY_URL_PORT));
+ return String.format(Locale.ROOT, GATEWAY_URL_TEMPLATE, protocol, host, port);
+ }
+
+ public String getGatewayJmxUrl() {
+ final String host = configuration.getProperty(PARAM_GATEWAY_URL_HOST, DEFAULT_GATEWAY_URL_HOST);
+ final int port = Integer.parseInt(configuration.getProperty(PARAM_GATEWAY_JMX_PORT, DEFAULT_GATEWAY_JMX_PORT));
+ return String.format(Locale.ROOT, GATEWAY_JMX_URL_TEMPLATE, host, port);
+ }
+
+ public String getGatewayPath() {
+ return configuration.getProperty(PARAM_GATEWAY_PATH, DEFAULT_GATEWAY_PATH);
+ }
+
+ public String getGatewayUser() {
+ return configuration.getProperty(PARAM_GATEWAY_USER, DEFAULT_GATEWAY_USER);
+ }
+
+ public String getGatewayPassword() {
+ return configuration.getProperty(PARAM_GATEWAY_PW, DEFAULT_GATEWAY_PW);
+ }
+
+ /* Reporting */
+
+ public long getReportGenerationPeriod() {
+ return Long.parseLong(configuration.getProperty(PARAM_REPORT_GENERATION_PERIOD, DEFAULT_REPORT_GENERATION_PERIOD));
+ }
+
+ public String getReportingTargetFolder() {
+ final String targetFolder = System.getProperty(PARAM_REPORT_GENERATION_TARGET_FOLDER);
+ return StringUtils.isBlank(targetFolder) ? configuration.getProperty(PARAM_REPORT_GENERATION_TARGET_FOLDER) : targetFolder;
+ }
+
+ public boolean isReportingEngineEnabled(String engineType) {
+ return Boolean.parseBoolean(configuration.getProperty(REPORT_GENERATION_PREFIX + engineType + PARAM_ENABLED_POSTFIX, "false"));
+ }
+
+ /* Use case */
+
+ public boolean isUseCaseEnabled(String useCase) {
+ return Boolean.parseBoolean(configuration.getProperty(USE_CASE_PREFIX + useCase + PARAM_ENABLED_POSTFIX, "false"));
+ }
+
+ public String getUseCaseTopology(String useCase, String topologyType) {
+ return configuration.getProperty(USE_CASE_PREFIX + useCase + PARAM_USE_CASE_TOPOLOGY_POSTFIX + topologyType, defaultUseCaseMap.get(useCase).get(topologyType));
+ }
+
+ public String getUseCaseUrl(String useCase, String topologyType) {
+ return getGatewayUrl() + URL_PATH_SEPARATOR + getGatewayPath() + URL_PATH_SEPARATOR + getUseCaseTopology(useCase, topologyType);
+ }
+
+ public String getUseCaseParam(String useCase, String param) {
+ return configuration.getProperty(USE_CASE_PREFIX + useCase + "." + param);
+ }
+
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestLifeCyleListener.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestLifeCyleListener.java
new file mode 100644
index 0000000..2e4b5f4
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestLifeCyleListener.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.knox.gateway.performance.test;
+
+public interface PerformanceTestLifeCyleListener {
+
+ void onFinish();
+
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestMessages.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestMessages.java
new file mode 100644
index 0000000..4cbe5b0
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestMessages.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.knox.gateway.performance.test;
+
+import org.apache.knox.gateway.i18n.messages.Message;
+import org.apache.knox.gateway.i18n.messages.MessageLevel;
+import org.apache.knox.gateway.i18n.messages.Messages;
+import org.apache.knox.gateway.i18n.messages.StackTrace;
+import org.apache.knox.gateway.performance.test.knoxtoken.KnoxTokenAction;
+
+@Messages(logger = "org.apache.knox.gateway.performance.test")
+public interface PerformanceTestMessages {
+
+ @Message(level = MessageLevel.INFO, text = "Running Knox Token Workers on {0} threads ...")
+ void runKnoxTokenWorkers(int numOfThreads);
+
+ @Message(level = MessageLevel.INFO, text = "I am a Knox Token {0} thread")
+ void knoxTokenWorkerName(KnoxTokenAction action);
+
+ @Message(level = MessageLevel.INFO, text = "Knox token worker thread finished")
+ void finishKnoxTokenWorker();
+
+ @Message(level = MessageLevel.ERROR, text = "Failed to run {0} Knox token worker: {1}")
+ void failedToRunKnoxTokenWorker(KnoxTokenAction action, String errorMessage, @StackTrace(level = MessageLevel.DEBUG) Exception e);
+
+ @Message(level = MessageLevel.ERROR, text = "Failed to execute {0} Knox token action: {1}")
+ void failedToExecuteKnoxTokenAction(KnoxTokenAction action, String errorMessage, @StackTrace(level = MessageLevel.DEBUG) Exception e);
+
+ @Message(level = MessageLevel.INFO, text = "Sleeping {0} seconds...")
+ void sleep(long sleepTime);
+
+ @Message(level = MessageLevel.INFO, text = "Acquiring Knox token...")
+ void acquireKnoxToken();
+
+ @Message(level = MessageLevel.INFO, text = "Acquired Knox token")
+ void acquiredKnoxToken();
+
+ @Message(level = MessageLevel.INFO, text = "Renewing Knox token {0}")
+ void renewKnoxToken(String knoxToken);
+
+ @Message(level = MessageLevel.INFO, text = "Renewed Knox token: {0}")
+ void renewedKnoxToken(String expiration);
+
+ @Message(level = MessageLevel.INFO, text = "Failed to renew Knox token: {0}")
+ void failedToRenewKnoxToken(String error);
+
+ @Message(level = MessageLevel.INFO, text = "There is no token to be renewed yet")
+ void nothingToRenew();
+
+ @Message(level = MessageLevel.INFO, text = "Using Knox token {0}")
+ void useKnoxToken(String knoxToken);
+
+ @Message(level = MessageLevel.INFO, text = "Failed to use Knox token: {0}")
+ void failedToUseKnoxToken(String error);
+
+ @Message(level = MessageLevel.INFO, text = "There is no token to be used yet")
+ void nothingToUse();
+
+ @Message(level = MessageLevel.ERROR, text = "Error while generating {0} report: {1}")
+ void failedToGenerateReport(String reportType, String errorMessage, @StackTrace(level = MessageLevel.DEBUG) Exception e);
+
+ @Message(level = MessageLevel.INFO, text = "Metrics reporter is shut down")
+ void shutDownMetricsReporter();
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestRunner.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestRunner.java
new file mode 100644
index 0000000..48caadd
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestRunner.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.knox.gateway.performance.test;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.Set;
+
+import org.apache.knox.gateway.performance.test.reporting.GatewayMetricsReporter;
+
+public class PerformanceTestRunner {
+
+ public static void main(String[] args) throws Exception {
+ final PerformanceTestConfiguration configuration = new PerformanceTestConfiguration(args[0]);
+ final ResponseTimeCache responseTimeCache = new ResponseTimeCache();
+ final GatewayMetricsReporter metricsReporter = new GatewayMetricsReporter(configuration, responseTimeCache);
+ metricsReporter.start();
+ final List<PerformanceTestLifeCyleListener> lifeCyleListeners = Arrays.asList(metricsReporter);
+ for (UseCaseRunner useCaseRunner : getUseCaseRunners()) {
+ if (configuration.isUseCaseEnabled(useCaseRunner.getUseCaseName())) {
+ useCaseRunner.setResponseTimeCache(responseTimeCache);
+ useCaseRunner.execute(configuration, lifeCyleListeners);
+ }
+ }
+ }
+
+ private static Set<UseCaseRunner> getUseCaseRunners() {
+ final Set<UseCaseRunner> useCaseRunners = new HashSet<>();
+ ServiceLoader.load(UseCaseRunner.class).forEach((useCaseRunner) -> useCaseRunners.add(useCaseRunner));
+ return useCaseRunners;
+ }
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/ResponseTimeCache.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/ResponseTimeCache.java
new file mode 100644
index 0000000..a309699
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/ResponseTimeCache.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.knox.gateway.performance.test;
+
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.stream.Collectors;
+
+public class ResponseTimeCache {
+
+ private final Queue<Long> acquireResponseTimes = new ConcurrentLinkedQueue<>();
+ private final Queue<Long> renewResponseTimes = new ConcurrentLinkedQueue<>();
+
+ public void saveAcquireResponseTime(long getResponseTime) {
+ acquireResponseTimes.offer(getResponseTime);
+ }
+
+ public void saveRenewResponseTime(long renewResponseTime) {
+ renewResponseTimes.offer(renewResponseTime);
+ }
+
+ public List<Long> listAcquireResponseTimes() {
+ return this.acquireResponseTimes.stream().collect(Collectors.toList());
+ }
+
+ public List<Long> listRenewResponseTimes() {
+ return this.renewResponseTimes.stream().collect(Collectors.toList());
+ }
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/UseCaseRunner.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/UseCaseRunner.java
new file mode 100644
index 0000000..8cda4c4
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/UseCaseRunner.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.knox.gateway.performance.test;
+
+import java.util.List;
+
+public interface UseCaseRunner {
+
+ String getUseCaseName();
+
+ void setResponseTimeCache(ResponseTimeCache responseTimeCache);
+
+ void execute(PerformanceTestConfiguration configuration, List<PerformanceTestLifeCyleListener> lifeCyleListeners);
+
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenAction.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenAction.java
new file mode 100644
index 0000000..147894d
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenAction.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.knox.gateway.performance.test.knoxtoken;
+
+public enum KnoxTokenAction {
+ ACQUIRE, RENEW, USE_TOKEN;
+
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenCache.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenCache.java
new file mode 100644
index 0000000..6f96cee
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenCache.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.knox.gateway.performance.test.knoxtoken;
+
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+class KnoxTokenCache {
+ private static final int CAPACITY = 500;
+ private final Queue<String> knoxTokens = new ConcurrentLinkedQueue<>();
+
+ void saveKnoxToken(String knoxToken) {
+ cleanIfNecessary();
+ this.knoxTokens.offer(knoxToken);
+ }
+
+ private void cleanIfNecessary() {
+ if (knoxTokens.size() == CAPACITY) {
+ knoxTokens.clear();
+ }
+ }
+
+ String getKnoxToken() {
+ return knoxTokens.poll();
+ }
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenUseCaseRunner.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenUseCaseRunner.java
new file mode 100644
index 0000000..1bc979b
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenUseCaseRunner.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.knox.gateway.performance.test.knoxtoken;
+
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.performance.test.ExecutorServiceUtils;
+import org.apache.knox.gateway.performance.test.PerformanceTestConfiguration;
+import org.apache.knox.gateway.performance.test.PerformanceTestLifeCyleListener;
+import org.apache.knox.gateway.performance.test.PerformanceTestMessages;
+import org.apache.knox.gateway.performance.test.ResponseTimeCache;
+import org.apache.knox.gateway.performance.test.UseCaseRunner;
+
+public class KnoxTokenUseCaseRunner implements UseCaseRunner {
+
+ public static final String USE_CASE_NAME = "knoxtoken";
+
+ private static final PerformanceTestMessages LOG = MessagesFactory.get(PerformanceTestMessages.class);
+ private static final String PARAM_NUMBER_OF_THREADS = "numOfThreads";
+
+ private final KnoxTokenCache knoxTokenCache = new KnoxTokenCache();
+ private ResponseTimeCache responseTimeCache;
+ private ExecutorService pool;
+
+ @Override
+ public void setResponseTimeCache(ResponseTimeCache responseTimeCache) {
+ this.responseTimeCache = responseTimeCache;
+ }
+
+ @Override
+ public void execute(PerformanceTestConfiguration configuration, List<PerformanceTestLifeCyleListener> lifeCyleListeners) {
+ final ThreadFactory tfthreadFactory = new BasicThreadFactory.Builder().namingPattern("KnoxTokenUseCaseRunner-%d").build();
+ final int numberOfThreads = Integer.parseInt(configuration.getUseCaseParam(getUseCaseName(), PARAM_NUMBER_OF_THREADS));
+ LOG.runKnoxTokenWorkers(numberOfThreads);
+ pool = Executors.newFixedThreadPool(numberOfThreads + 2, tfthreadFactory);
+ try {
+ for (int i = 0; i < numberOfThreads; i++) {
+ pool.submit(new KnoxTokenWorkerThread(configuration, KnoxTokenAction.ACQUIRE, knoxTokenCache, responseTimeCache));
+ }
+
+ // 2 other jobs to renew and use qcquired Knox Tokens
+ pool.submit(new KnoxTokenWorkerThread(configuration, KnoxTokenAction.RENEW, knoxTokenCache, responseTimeCache));
+ pool.submit(new KnoxTokenWorkerThread(configuration, KnoxTokenAction.USE_TOKEN, knoxTokenCache, responseTimeCache));
+ } catch (Exception ex) {
+ ExecutorServiceUtils.shutdownAndAwaitTermination(pool, 10, TimeUnit.SECONDS);
+ } finally {
+ final long testDuration = Long.parseLong(configuration.getUseCaseParam(getUseCaseName(), KnoxTokenWorkerThread.PARAM_DURATION_IN_SECONDS));
+ final long upperBound = Long.parseLong(configuration.getUseCaseParam(getUseCaseName(), KnoxTokenWorkerThread.PARAM_REQUEST_DELAY_UPPERBOUND));
+ ExecutorServiceUtils.shutdownAndAwaitTermination(pool, testDuration + upperBound + 10, TimeUnit.SECONDS);
+ lifeCyleListeners.forEach(lifeCyleListener -> lifeCyleListener.onFinish());
+ }
+ }
+
+ @Override
+ public String getUseCaseName() {
+ return USE_CASE_NAME;
+ }
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenWorkerThread.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenWorkerThread.java
new file mode 100644
index 0000000..ae3a91b
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenWorkerThread.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.knox.gateway.performance.test.knoxtoken;
+
+
+import static org.apache.knox.gateway.performance.test.knoxtoken.KnoxTokenUseCaseRunner.USE_CASE_NAME;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.performance.test.PerformanceTestConfiguration;
+import org.apache.knox.gateway.performance.test.PerformanceTestMessages;
+import org.apache.knox.gateway.performance.test.ResponseTimeCache;
+import org.apache.knox.gateway.services.security.token.TokenUtils;
+import org.apache.knox.gateway.shell.ErrorResponse;
+import org.apache.knox.gateway.shell.KnoxSession;
+import org.apache.knox.gateway.shell.KnoxShellException;
+import org.apache.knox.gateway.shell.hdfs.Hdfs;
+import org.apache.knox.gateway.shell.knox.token.Get;
+import org.apache.knox.gateway.shell.knox.token.Token;
+import org.apache.knox.gateway.shell.knox.token.TokenLifecycleResponse;
+import org.apache.knox.gateway.util.JsonUtils;
+
+@SuppressWarnings("PMD.DoNotUseThreads")
+public class KnoxTokenWorkerThread implements Runnable {
+ private static final PerformanceTestMessages LOG = MessagesFactory.get(PerformanceTestMessages.class);
+ static final String PARAM_DURATION_IN_SECONDS = "testDurationInSecs";
+ private static final String PARAM_REQUEST_DELAY_LOWERBOUND = "requestDelayLowerBoundInSecs";
+ static final String PARAM_REQUEST_DELAY_UPPERBOUND = "requestDelayUpperBoundInSecs";
+
+ private final PerformanceTestConfiguration configuration;
+ private final KnoxTokenAction action;
+ private final KnoxTokenCache knoxTokenCache;
+ private final ResponseTimeCache responseTimeCache;
+ private Instant startTime;
+
+ public KnoxTokenWorkerThread(PerformanceTestConfiguration configuration, KnoxTokenAction action, KnoxTokenCache knoxTokenCache, ResponseTimeCache responseTimeCache) {
+ this.configuration = configuration;
+ this.action = action;
+ this.knoxTokenCache = knoxTokenCache;
+ this.responseTimeCache = responseTimeCache;
+ }
+
+ @Override
+ public void run() {
+ try {
+ LOG.knoxTokenWorkerName(action);
+ final KnoxSession gatewayKnoxSession = KnoxSession.login(configuration.getUseCaseUrl(USE_CASE_NAME, "gateway"), configuration.getGatewayUser(),
+ configuration.getGatewayPassword());
+ final KnoxSession tokenBasedKnoxSession = KnoxSession.login(configuration.getUseCaseUrl(USE_CASE_NAME, "tokenbased"), configuration.getGatewayUser(),
+ configuration.getGatewayPassword());
+ final long testDuration = Long.parseLong(configuration.getUseCaseParam(USE_CASE_NAME, PARAM_DURATION_IN_SECONDS));
+ final long lowerBound = Long.parseLong(configuration.getUseCaseParam(USE_CASE_NAME, PARAM_REQUEST_DELAY_LOWERBOUND));
+ final long upperBound = Long.parseLong(configuration.getUseCaseParam(USE_CASE_NAME, PARAM_REQUEST_DELAY_UPPERBOUND));
+ this.startTime = Instant.now();
+ int requestCount = 0;
+ while (shouldRun(testDuration)) {
+ executeAction(gatewayKnoxSession, tokenBasedKnoxSession);
+
+ if (requestCount > 0) {
+ TimeUnit.SECONDS.sleep(calculateSleepTime(lowerBound, upperBound));
+ }
+ requestCount++;
+ }
+ } catch (Exception e) {
+ LOG.failedToRunKnoxTokenWorker(action, e.getMessage(), e);
+ } finally {
+ LOG.finishKnoxTokenWorker();
+ }
+ }
+
+ private void executeAction(final KnoxSession gatewayKnoxSession, final KnoxSession tokenBasedKnoxSession) {
+ try {
+ switch (this.action) {
+ case ACQUIRE:
+ knoxTokenCache.saveKnoxToken(acquireKnoxToken(gatewayKnoxSession));
+ break;
+ case RENEW:
+ renewKnoxToken(gatewayKnoxSession);
+ break;
+ case USE_TOKEN:
+ useKnoxToken(tokenBasedKnoxSession);
+ break;
+ default:
+ // NOP
+ break;
+ }
+ } catch (Exception e) {
+ LOG.failedToExecuteKnoxTokenAction(action, e.getMessage(), e);
+ }
+ }
+
+ private boolean shouldRun(long testDuration) {
+ final long timeElapsed = Duration.between(startTime, Instant.now()).getSeconds();
+ final boolean shouldRun = timeElapsed < testDuration;
+ return shouldRun;
+ }
+
+ private long calculateSleepTime(long lowerBound, long upperBound) {
+ final long sleepTime = ThreadLocalRandom.current().nextLong(KnoxTokenAction.ACQUIRE == this.action ? lowerBound : lowerBound * 2,
+ KnoxTokenAction.ACQUIRE == this.action ? upperBound : upperBound * 2);
+ LOG.sleep(sleepTime);
+ return sleepTime;
+ }
+
+ private String acquireKnoxToken(KnoxSession knoxSession) throws IOException {
+ LOG.acquireKnoxToken();
+ long getStart = System.currentTimeMillis();
+ final Get.Response getTokenResponse = Token.get(knoxSession).now();
+ final long getResponseTime = System.currentTimeMillis() - getStart;
+ LOG.acquiredKnoxToken();
+ responseTimeCache.saveAcquireResponseTime(getResponseTime);
+ final Map<String, String> tokenAsMap = JsonUtils.getMapFromJsonString(getTokenResponse.getString());
+ return tokenAsMap.get("access_token");
+ }
+
+ private void renewKnoxToken(KnoxSession knoxSession) throws Exception {
+ final String knoxTokenToRenew = this.knoxTokenCache.getKnoxToken();
+ if (knoxTokenToRenew != null) {
+ LOG.renewKnoxToken(TokenUtils.getTokenDisplayText(knoxTokenToRenew));
+ final long renewStart = System.currentTimeMillis();
+ final TokenLifecycleResponse renewResponse = Token.renew(knoxSession, knoxTokenToRenew).now();
+ final long renewResponseTime = System.currentTimeMillis() - renewStart;
+ responseTimeCache.saveRenewResponseTime(renewResponseTime);
+ final Map<String, String> map = JsonUtils.getMapFromJsonString(renewResponse.getString());
+ boolean renewed = Boolean.parseBoolean(map.get("renewed"));
+ if (renewed) {
+ LOG.renewedKnoxToken(Instant.ofEpochMilli(Long.parseLong(map.get("expires"))).toString());
+ } else {
+ LOG.failedToRenewKnoxToken(map.get("error"));
+ }
+ } else {
+ LOG.nothingToRenew();
+ }
+ }
+
+ private void useKnoxToken(KnoxSession knoxSession) {
+ try {
+ final String knoxToken = this.knoxTokenCache.getKnoxToken();
+ if (knoxToken != null) {
+ LOG.useKnoxToken(TokenUtils.getTokenDisplayText(knoxToken));
+ Hdfs.ls(knoxSession).knoxToken(knoxToken).now();
+ } else {
+ LOG.nothingToUse();
+ }
+ } catch (KnoxShellException e) {
+ final ErrorResponse errorResponse = (ErrorResponse) e.getCause();
+ final int errorCode = errorResponse.getResponse().getStatusLine().getStatusCode();
+ // if unauthorized -> it's a failure (every other error code is irrelevant here)
+ if (HttpServletResponse.SC_UNAUTHORIZED == errorCode) {
+ LOG.failedToUseKnoxToken(e.getMessage());
+ }
+ }
+ }
+
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/AbstractReportEngine.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/AbstractReportEngine.java
new file mode 100644
index 0000000..dd6b44f
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/AbstractReportEngine.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.knox.gateway.performance.test.reporting;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.performance.test.PerformanceTestConfiguration;
+import org.apache.knox.gateway.performance.test.PerformanceTestMessages;
+
+abstract class AbstractReportEngine implements ReportEngine {
+ private static final PerformanceTestMessages LOG = MessagesFactory.get(PerformanceTestMessages.class);
+ private final DateFormat REPORT_FILE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.ROOT);
+ private final PerformanceTestConfiguration configuration;
+ private final Path reportFolderPath;
+
+ AbstractReportEngine(PerformanceTestConfiguration configuration) throws IOException {
+ this.configuration = configuration;
+ this.reportFolderPath = Paths.get(configuration.getReportingTargetFolder(), getType());
+ if (!reportFolderPath.toFile().exists()) {
+ Files.createDirectories(reportFolderPath);
+ }
+ }
+
+ @Override
+ public void generateReport(String reportName, Map<String, Object> reportMaterial) {
+ if (configuration.isReportingEngineEnabled(getType())) {
+ String reportFile = reportName + "." + REPORT_FILE_DATE_FORMAT.format(new Date()) + "." + getType();
+ try {
+ FileUtils.writeStringToFile(reportFolderPath.resolve(reportFile).toFile(), getContent(reportMaterial), StandardCharsets.UTF_8);
+ } catch (Exception e) {
+ LOG.failedToGenerateReport(getType(), e.getMessage(), e);
+ }
+ }
+ }
+
+ public abstract String getContent(Map<String, Object> reportMaterial) throws Exception;
+
+ public abstract String getType();
+
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/GatewayMetricsReporter.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/GatewayMetricsReporter.java
new file mode 100644
index 0000000..ee5d69e
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/GatewayMetricsReporter.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.knox.gateway.performance.test.reporting;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanInfo;
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.apache.commons.math3.stat.StatUtils;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.performance.test.ExecutorServiceUtils;
+import org.apache.knox.gateway.performance.test.PerformanceTestConfiguration;
+import org.apache.knox.gateway.performance.test.PerformanceTestLifeCyleListener;
+import org.apache.knox.gateway.performance.test.PerformanceTestMessages;
+import org.apache.knox.gateway.performance.test.ResponseTimeCache;
+
+public class GatewayMetricsReporter implements PerformanceTestLifeCyleListener {
+ private static final PerformanceTestMessages LOG = MessagesFactory.get(PerformanceTestMessages.class);
+ private static final String TOKEN_STATE_STATISTICS_OBJECT_NAME = "metrics:type=Statistics,name=TokenStateService";
+ private static final String TIMERS_OBJECT_NAME = "metrics:type=timers,name=client*";
+ private static final String HEAP_GAUGES_OBJECT_NAME = "metrics:type=gauges,name=heap*";
+ private final PerformanceTestConfiguration configuration;
+ private final ResponseTimeCache responseTimeCache;
+ private final JMXConnector jmxConnector;
+ private final ScheduledExecutorService executor;
+ private final Map<String, String> objectNamesToQuery;
+ private final List<ReportEngine> reportEngines;
+
+ public GatewayMetricsReporter(PerformanceTestConfiguration configuration, ResponseTimeCache responseTimeCache) throws IOException {
+ this.configuration = configuration;
+ this.responseTimeCache = responseTimeCache;
+ final JMXServiceURL serviceUrl = new JMXServiceURL(configuration.getGatewayJmxUrl());
+ jmxConnector = JMXConnectorFactory.connect(serviceUrl, null);
+ final ThreadFactory threadFactory = new BasicThreadFactory.Builder().namingPattern("GatewayMetricsReporter-%d").build();
+ this.executor = Executors.newSingleThreadScheduledExecutor(threadFactory);
+ this.objectNamesToQuery = new HashMap<>();
+ objectNamesToQuery.put("tokenStateStatistics", TOKEN_STATE_STATISTICS_OBJECT_NAME);
+ objectNamesToQuery.put("timers", TIMERS_OBJECT_NAME);
+ objectNamesToQuery.put("heapGauges", HEAP_GAUGES_OBJECT_NAME);
+ this.reportEngines = Arrays.asList(new JsonReportEngine(configuration), new YamlReportEngine(configuration));
+ }
+
+ public void start() throws IOException {
+ final MBeanServerConnection mbeanServerConnection = jmxConnector.getMBeanServerConnection();
+ executor.scheduleAtFixedRate(() -> generateReports(mbeanServerConnection), configuration.getReportGenerationPeriod(), configuration.getReportGenerationPeriod(),
+ TimeUnit.SECONDS);
+ }
+
+ @Override
+ public void onFinish() {
+ ExecutorServiceUtils.shutdownAndAwaitTermination(executor, 10, TimeUnit.SECONDS);
+ try {
+ jmxConnector.close();
+ } catch (IOException e) {
+ // NOP
+ }
+ LOG.shutDownMetricsReporter();
+ }
+
+ private void generateReports(MBeanServerConnection mbeanConn) {
+ printResponseTimes();
+ printMetrics(mbeanConn);
+ }
+
+ private void printResponseTimes() {
+ final Map<String, Object> responseTimesMap = new HashMap<>();
+ setReponseTimesMetrics(responseTimesMap, responseTimeCache.listAcquireResponseTimes(), "acquireResponseTimes");
+ setReponseTimesMetrics(responseTimesMap, responseTimeCache.listRenewResponseTimes(), "renewResponseTimes");
+ for (ReportEngine reportEngine : reportEngines) {
+ reportEngine.generateReport("responseTimes", responseTimesMap);
+ }
+ }
+
+ private void setReponseTimesMetrics(Map<String, Object> responseTimesMap, List<Long> responseTimes, String metricsName) {
+ final Map<String, Object> statistics = new HashMap<>();
+ statistics.put("_data", responseTimes);
+ final double[] doubleArrayResponseTimes = responseTimes.stream().mapToDouble(responseTime -> responseTime).toArray();
+ statistics.put("max", StatUtils.max(doubleArrayResponseTimes));
+ statistics.put("min", StatUtils.min(doubleArrayResponseTimes));
+ statistics.put("mean", StatUtils.mean(doubleArrayResponseTimes));
+ statistics.put("mode", StatUtils.mode(doubleArrayResponseTimes));
+ statistics.put("geometricMean", StatUtils.geometricMean(doubleArrayResponseTimes));
+ responseTimesMap.put(metricsName, statistics);
+ }
+
+ private void printMetrics(MBeanServerConnection mbeanConn) {
+ try {
+ for (Map.Entry<String, String> objectNameEntry : objectNamesToQuery.entrySet()) {
+ Map<String, Object> metrics = new HashMap<>();
+ for (ObjectName bean : mbeanConn.queryNames(ObjectName.getInstance(objectNameEntry.getValue()), null)) {
+ metrics.put(bean.getCanonicalName(), getAttributes(mbeanConn, bean, mbeanConn.getMBeanInfo(bean)));
+ }
+ for (ReportEngine reportEngine : reportEngines) {
+ reportEngine.generateReport(objectNameEntry.getKey(), metrics);
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private Map<String, Object> getAttributes(MBeanServerConnection mbeanConn, ObjectName bean, MBeanInfo mbeanInfo) throws Exception {
+ final Map<String, Object> attributeMap = new HashMap<>();
+ for (MBeanAttributeInfo mbeanAttribute : mbeanInfo.getAttributes()) {
+ String attributeName = mbeanAttribute.getName();
+ Object attributeValue = mbeanConn.getAttribute(bean, attributeName);
+ attributeMap.put(attributeName, attributeValue);
+ }
+ return attributeMap;
+ }
+
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/JsonReportEngine.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/JsonReportEngine.java
new file mode 100644
index 0000000..d9f9fda
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/JsonReportEngine.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.knox.gateway.performance.test.reporting;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.knox.gateway.performance.test.PerformanceTestConfiguration;
+import org.apache.knox.gateway.util.JsonUtils;
+
+class JsonReportEngine extends AbstractReportEngine {
+
+ private static final String TYPE = "json";
+
+ JsonReportEngine(PerformanceTestConfiguration configuration) throws IOException {
+ super(configuration);
+ }
+
+ @Override
+ public String getContent(Map<String, Object> reportMaterial) throws Exception {
+ return JsonUtils.renderAsJsonString(reportMaterial);
+ }
+
+ @Override
+ public String getType() {
+ return TYPE;
+ }
+
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/ReportEngine.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/ReportEngine.java
new file mode 100644
index 0000000..7c498bd
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/ReportEngine.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.knox.gateway.performance.test.reporting;
+
+import java.util.Map;
+
+public interface ReportEngine {
+
+ void generateReport(String reportName, Map<String, Object> reportMaterial);
+
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/YamlReportEngine.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/YamlReportEngine.java
new file mode 100644
index 0000000..168fd26
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/YamlReportEngine.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.knox.gateway.performance.test.reporting;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.knox.gateway.performance.test.PerformanceTestConfiguration;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+
+class YamlReportEngine extends AbstractReportEngine {
+
+ private static final String TYPE = "yaml";
+ private final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
+
+ YamlReportEngine(PerformanceTestConfiguration configuration) throws IOException {
+ super(configuration);
+ }
+
+ @Override
+ public String getContent(Map<String, Object> reportMaterial) throws Exception {
+ return mapper.writeValueAsString(reportMaterial);
+ }
+
+ @Override
+ public String getType() {
+ return TYPE;
+ }
+
+}
diff --git a/gateway-performance-test/src/main/resources/META-INF/services/org.apache.knox.gateway.performance.test.UseCaseRunner b/gateway-performance-test/src/main/resources/META-INF/services/org.apache.knox.gateway.performance.test.UseCaseRunner
new file mode 100644
index 0000000..af9e34f
--- /dev/null
+++ b/gateway-performance-test/src/main/resources/META-INF/services/org.apache.knox.gateway.performance.test.UseCaseRunner
@@ -0,0 +1,19 @@
+##########################################################################
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License 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.
+##########################################################################
+
+org.apache.knox.gateway.performance.test.knoxtoken.KnoxTokenUseCaseRunner
\ No newline at end of file
diff --git a/gateway-performance-test/src/test/resources/performance.test.configuration.properties b/gateway-performance-test/src/test/resources/performance.test.configuration.properties
new file mode 100644
index 0000000..43ff695
--- /dev/null
+++ b/gateway-performance-test/src/test/resources/performance.test.configuration.properties
@@ -0,0 +1,35 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License 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.
+
+# Gateway connection related properties
+perf.test.gateway.url.protocol=https
+perf.test.gateway.url.host=localhost
+perf.test.gateway.url.port=8443
+perf.test.gateway.jmx.port=8888
+
+# report generation related properties
+perf.test.report.generation.periodInSecs=30
+perf.test.report.generation.json.enabled=true
+perf.test.report.generation.yaml.enabled=true
+
+# Knox Token use case related properties
+perf.test.usecase.knoxtoken.enabled=true
+perf.test.usecase.knoxtoken.topology.gateway=sandbox
+perf.test.usecase.knoxtoken.topology.tokenbased=tokenbased
+perf.test.usecase.knoxtoken.numOfThreads=3
+perf.test.usecase.knoxtoken.testDurationInSecs=60
+perf.test.usecase.knoxtoken.requestDelayLowerBoundInSecs=5
+perf.test.usecase.knoxtoken.requestDelayUpperBoundInSecs=10
\ No newline at end of file
diff --git a/gateway-performance-test/src/test/resources/performanceTest-log4j.properties b/gateway-performance-test/src/test/resources/performanceTest-log4j.properties
new file mode 100644
index 0000000..3fa6534
--- /dev/null
+++ b/gateway-performance-test/src/test/resources/performanceTest-log4j.properties
@@ -0,0 +1,35 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License 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.
+
+app.log.dir=gateway-performance-test/target/logs
+app.log.file=performanceTest.log
+
+log4j.rootLogger=ERROR, stdout, drfa
+
+log4j.logger.org.apache.knox.gateway.performance.test=INFO
+log4j.logger.org.apache.http.client=INFO
+log4j.logger.org.apache.http.impl.client=INFO
+log4j.logger.org.apache.http.impl.conn=INFO
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} - [%t] - %m%n
+
+log4j.appender.drfa=org.apache.log4j.DailyRollingFileAppender
+log4j.appender.drfa.File=${app.log.dir}/${app.log.file}
+log4j.appender.drfa.DatePattern=.yyyy-MM-dd
+log4j.appender.drfa.layout=org.apache.log4j.PatternLayout
+log4j.appender.drfa.layout.ConversionPattern=%d{ISO8601} - [%t] - %m%n
diff --git a/gateway-release/pom.xml b/gateway-release/pom.xml
index 32f1203..4271b4c 100644
--- a/gateway-release/pom.xml
+++ b/gateway-release/pom.xml
@@ -449,5 +449,9 @@
<groupId>org.apache.knox</groupId>
<artifactId>knox-homepage-ui</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.knox</groupId>
+ <artifactId>gateway-service-metadata</artifactId>
+ </dependency>
</dependencies>
</project>
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreService.java
index 546d53d..559ac10 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreService.java
@@ -69,7 +69,7 @@ import javax.crypto.spec.SecretKeySpec;
public class DefaultKeystoreService implements KeystoreService {
private static final String DN_TEMPLATE = "CN={0},OU=Test,O=Hadoop,L=Test,ST=Test,C=US";
- private static final String CREDENTIALS_SUFFIX = "-credentials.jceks";
+ public static final String CREDENTIALS_SUFFIX = "-credentials.jceks";
private static final String CREDENTIALS_STORE_TYPE = "JCEKS";
private static final String CERT_GEN_MODE = "hadoop.gateway.cert.gen.mode";
private static final String CERT_GEN_MODE_LOCALHOST = "localhost";
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/TokenStateServiceStatistics.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/TokenStateServiceStatistics.java
new file mode 100644
index 0000000..4085762
--- /dev/null
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/TokenStateServiceStatistics.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.knox.gateway.services.token;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+
+import javax.management.StandardMBean;
+
+import org.apache.knox.gateway.services.security.token.TokenStateServiceStatisticsMBean;
+
+public class TokenStateServiceStatistics extends StandardMBean implements TokenStateServiceStatisticsMBean {
+
+ public enum KeystoreInteraction {
+ SAVE_ALIAS("saveAlias"), REMOVE_ALIAS("removeAlias"), GET_PASSWORD("getPassword"), GET_ALIAS("getAlias");
+
+ private final String key;
+
+ KeystoreInteraction(String key) {
+ this.key = key;
+ }
+
+ public String getKey() {
+ return key;
+ }
+ };
+
+ private final AtomicInteger numberOfTokensAdded = new AtomicInteger(0);
+ private final AtomicInteger numberOfTokensRenewed = new AtomicInteger(0);
+ private final Map<String, AtomicInteger> keystoreInteractions = new ConcurrentHashMap<>();
+ private final AtomicLong gatewayCredentialsFileSize = new AtomicLong(0L);
+
+ public TokenStateServiceStatistics() {
+ super(TokenStateServiceStatisticsMBean.class, false);
+ }
+
+ public void addToken() {
+ this.numberOfTokensAdded.incrementAndGet();
+ }
+
+ public void renewToken() {
+ this.numberOfTokensRenewed.incrementAndGet();
+ }
+
+ public void interactKeystore(KeystoreInteraction keystoreInteraction) {
+ this.keystoreInteractions.putIfAbsent(keystoreInteraction.getKey(), new AtomicInteger(0));
+ this.keystoreInteractions.get(keystoreInteraction.getKey()).incrementAndGet();
+ }
+
+ public void setGatewayCredentialsFileSize(long gatewayCredentialsFileSize) {
+ this.gatewayCredentialsFileSize.set(gatewayCredentialsFileSize);
+ }
+
+ @Override
+ public int getNumberOfTokensAdded() {
+ return this.numberOfTokensAdded.get();
+ }
+
+ @Override
+ public int getNumberOfTokensRenewed() {
+ return this.numberOfTokensRenewed.get();
+ }
+
+ @Override
+ public Map<String, Integer> getKeystoreInteractions() {
+ return this.keystoreInteractions.entrySet().stream().collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue().get()));
+ }
+
+ @Override
+ public long getGatewayCredentialsFileSize() {
+ return this.gatewayCredentialsFileSize.get();
+ }
+}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateService.java
index 8b44336..598bd12 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateService.java
@@ -20,12 +20,16 @@ import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.services.ServiceLifecycleException;
import org.apache.knox.gateway.services.security.AliasService;
import org.apache.knox.gateway.services.security.AliasServiceException;
+import org.apache.knox.gateway.services.security.impl.DefaultKeystoreService;
import org.apache.knox.gateway.services.security.token.UnknownTokenException;
import org.apache.knox.gateway.services.token.state.JournalEntry;
import org.apache.knox.gateway.services.token.state.TokenStateJournal;
+import org.apache.knox.gateway.services.token.TokenStateServiceStatistics;
import org.apache.knox.gateway.services.token.impl.state.TokenStateJournalFactory;
import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -55,6 +59,8 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
private TokenStateJournal journal;
+ private Path gatewayCredentialsFilePath;
+
public void setAliasService(AliasService aliasService) {
this.aliasService = aliasService;
}
@@ -99,6 +105,11 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
if (statePersistenceInterval > 0) {
statePersistenceScheduler = Executors.newScheduledThreadPool(1);
}
+
+ if (tokenStateServiceStatistics != null) {
+ this.gatewayCredentialsFilePath = Paths.get(config.getGatewayKeystoreDir()).resolve(AliasService.NO_CLUSTER_NAME + DefaultKeystoreService.CREDENTIALS_SUFFIX);
+ tokenStateServiceStatistics.setGatewayCredentialsFileSize(this.gatewayCredentialsFilePath.toFile().length());
+ }
}
@Override
@@ -151,6 +162,10 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
try {
aliasService.addAliasesForCluster(AliasService.NO_CLUSTER_NAME, aliases);
+ if (tokenStateServiceStatistics != null) {
+ tokenStateServiceStatistics.interactKeystore(TokenStateServiceStatistics.KeystoreInteraction.SAVE_ALIAS);
+ tokenStateServiceStatistics.setGatewayCredentialsFileSize(this.gatewayCredentialsFilePath.toFile().length());
+ }
for (String tokenId : tokenIds) {
log.createdTokenStateAliases(tokenId);
// After the aliases have been successfully persisted, remove their associated state from the journal
@@ -202,9 +217,7 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
// If there is no result from the in-memory collection, proceed to check the alias service
if (result < 1L) {
try {
- char[] maxLifetimeStr =
- aliasService.getPasswordFromAliasForCluster(AliasService.NO_CLUSTER_NAME,
- tokenId + TOKEN_MAX_LIFETIME_POSTFIX);
+ char[] maxLifetimeStr = getPasswordUsingAliasService(tokenId + TOKEN_MAX_LIFETIME_POSTFIX);
if (maxLifetimeStr != null) {
result = Long.parseLong(new String(maxLifetimeStr));
}
@@ -215,6 +228,14 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
return result;
}
+ private char[] getPasswordUsingAliasService(String tokenId) throws AliasServiceException {
+ char[] password = aliasService.getPasswordFromAliasForCluster(AliasService.NO_CLUSTER_NAME, tokenId);
+ if (tokenStateServiceStatistics != null) {
+ tokenStateServiceStatistics.interactKeystore(TokenStateServiceStatistics.KeystoreInteraction.GET_PASSWORD);
+ }
+ return password;
+ }
+
@Override
public long getTokenExpiration(String tokenId, boolean validate) throws UnknownTokenException {
// Check the in-memory collection first, to avoid costly keystore access when possible
@@ -233,7 +254,7 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
// If there is no associated state in the in-memory cache, proceed to check the alias service
long expiration = 0;
try {
- char[] expStr = aliasService.getPasswordFromAliasForCluster(AliasService.NO_CLUSTER_NAME, tokenId);
+ char[] expStr = getPasswordUsingAliasService(tokenId);
if (expStr == null) {
throw new UnknownTokenException(tokenId);
}
@@ -256,7 +277,7 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
// If it's not in the cache, then check the underlying alias
if (isUnknown) {
try {
- isUnknown = (aliasService.getPasswordFromAliasForCluster(AliasService.NO_CLUSTER_NAME, tokenId) == null);
+ isUnknown = (getPasswordUsingAliasService(tokenId) == null);
} catch (AliasServiceException e) {
log.errorAccessingTokenState(tokenId, e);
}
@@ -296,6 +317,10 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
log.removingTokenStateAliases();
try {
aliasService.removeAliasesForCluster(AliasService.NO_CLUSTER_NAME, aliasesToRemove);
+ if (tokenStateServiceStatistics != null) {
+ tokenStateServiceStatistics.interactKeystore(TokenStateServiceStatistics.KeystoreInteraction.REMOVE_ALIAS);
+ tokenStateServiceStatistics.setGatewayCredentialsFileSize(this.gatewayCredentialsFilePath.toFile().length());
+ }
for (String tokenId : tokenIds) {
log.removedTokenStateAliases(tokenId);
}
@@ -313,23 +338,28 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
try {
aliasService.removeAliasForCluster(AliasService.NO_CLUSTER_NAME, tokenId);
aliasService.addAliasForCluster(AliasService.NO_CLUSTER_NAME, tokenId, String.valueOf(expiration));
+ if (tokenStateServiceStatistics != null) {
+ tokenStateServiceStatistics.interactKeystore(TokenStateServiceStatistics.KeystoreInteraction.REMOVE_ALIAS);
+ tokenStateServiceStatistics.interactKeystore(TokenStateServiceStatistics.KeystoreInteraction.SAVE_ALIAS);
+ tokenStateServiceStatistics.setGatewayCredentialsFileSize(this.gatewayCredentialsFilePath.toFile().length());
+ }
} catch (AliasServiceException e) {
log.failedToUpdateTokenExpiration(tokenId, e);
}
}
+
@Override
protected List<String> getTokens() {
List<String> tokenIds = null;
try {
List<String> allAliases = aliasService.getAliasesForCluster(AliasService.NO_CLUSTER_NAME);
-
- // Filter for the token state aliases, and extract the token ID
- tokenIds = allAliases.stream()
- .filter(a -> a.contains(TOKEN_MAX_LIFETIME_POSTFIX))
- .map(a -> a.substring(0, a.indexOf(TOKEN_MAX_LIFETIME_POSTFIX)))
- .collect(Collectors.toList());
+ if (tokenStateServiceStatistics != null) {
+ tokenStateServiceStatistics.interactKeystore(TokenStateServiceStatistics.KeystoreInteraction.GET_ALIAS);
+ }
+ // Filter for the token state aliases (exclude aliases ending with TOKEN_MAX_LIFETIME_POSTFIX)
+ tokenIds = allAliases.stream().filter(alias -> !alias.endsWith(TOKEN_MAX_LIFETIME_POSTFIX)).collect(Collectors.toList());
} catch (AliasServiceException e) {
log.errorAccessingTokenState(e);
}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateService.java
index 2361269..850ac07 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateService.java
@@ -16,15 +16,7 @@
*/
package org.apache.knox.gateway.services.token.impl;
-import org.apache.knox.gateway.config.GatewayConfig;
-import org.apache.knox.gateway.i18n.messages.MessagesFactory;
-import org.apache.knox.gateway.services.ServiceLifecycleException;
-import org.apache.knox.gateway.services.security.token.TokenStateService;
-import org.apache.knox.gateway.services.security.token.TokenUtils;
-import org.apache.knox.gateway.services.security.token.UnknownTokenException;
-import org.apache.knox.gateway.services.security.token.impl.JWT;
-import org.apache.knox.gateway.services.security.token.impl.JWTToken;
-
+import java.lang.management.ManagementFactory;
import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
@@ -37,6 +29,22 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.MBeanRegistrationException;
+import javax.management.MalformedObjectNameException;
+import javax.management.NotCompliantMBeanException;
+import javax.management.ObjectName;
+
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.apache.knox.gateway.services.security.token.TokenStateService;
+import org.apache.knox.gateway.services.security.token.TokenUtils;
+import org.apache.knox.gateway.services.security.token.UnknownTokenException;
+import org.apache.knox.gateway.services.security.token.impl.JWT;
+import org.apache.knox.gateway.services.security.token.impl.JWTToken;
+import org.apache.knox.gateway.services.token.TokenStateServiceStatistics;
+
/**
* In-Memory authentication token state management implementation.
*/
@@ -65,12 +73,24 @@ public class DefaultTokenStateService implements TokenStateService {
private final ScheduledExecutorService evictionScheduler = Executors.newScheduledThreadPool(1);
+ //token state MBean to store statistics (only initialized and used if JMX reporting is enabled)
+ protected TokenStateServiceStatistics tokenStateServiceStatistics;
+
@Override
public void init(final GatewayConfig config, final Map<String, String> options) throws ServiceLifecycleException {
tokenEvictionInterval = config.getKnoxTokenEvictionInterval();
tokenEvictionGracePeriod = config.getKnoxTokenEvictionGracePeriod();
permissiveValidationEnabled = config.isKnoxTokenPermissiveValidationEnabled();
+ if (config.isMetricsEnabled() && config.isJmxMetricsReportingEnabled()) {
+ try {
+ tokenStateServiceStatistics = new TokenStateServiceStatistics();
+ final ObjectName objectName = ObjectName.getInstance("metrics:type=Statistics,name=TokenStateService");
+ ManagementFactory.getPlatformMBeanServer().registerMBean(tokenStateServiceStatistics, objectName);
+ } catch (MalformedObjectNameException | InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) {
+ throw new ServiceLifecycleException("Could not register token state service MBean", e);
+ }
+ }
}
@Override
@@ -122,6 +142,9 @@ public class DefaultTokenStateService implements TokenStateService {
}
setMaxLifetime(tokenId, issueTime, maxLifetimeDuration);
log.addedToken(tokenId, getTimestampDisplay(expiration));
+ if (tokenStateServiceStatistics != null) {
+ tokenStateServiceStatistics.addToken();
+ }
}
@Override
@@ -154,17 +177,16 @@ public class DefaultTokenStateService implements TokenStateService {
@Override
public long getTokenExpiration(String tokenId, boolean validate) throws UnknownTokenException {
- long expiration;
-
if (validate) {
validateToken(tokenId);
}
+ long expiration = -1;
synchronized (tokenExpirations) {
- if (!tokenExpirations.containsKey(tokenId)) {
- throw new UnknownTokenException(tokenId);
- }
- expiration = tokenExpirations.get(tokenId);
+ expiration = tokenExpirations.getOrDefault(tokenId, -1L);
+ }
+ if (expiration == -1) {
+ throw new UnknownTokenException(tokenId);
}
return expiration;
@@ -199,6 +221,9 @@ public class DefaultTokenStateService implements TokenStateService {
expiration = System.currentTimeMillis() + renewInterval;
updateExpiration(tokenId, expiration);
log.renewedToken(tokenId, getTimestampDisplay(expiration));
+ if (tokenStateServiceStatistics != null) {
+ tokenStateServiceStatistics.renewToken();
+ }
} else {
log.renewalLimitExceeded(tokenId);
throw new IllegalArgumentException("The renewal limit for the token has been exceeded");
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/AbstractRequest.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/AbstractRequest.java
index ba2df30..e158919 100644
--- a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/AbstractRequest.java
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/AbstractRequest.java
@@ -37,6 +37,7 @@ public abstract class AbstractRequest<T> {
private static final String PARAMETER_NAME_DOAS = "doAs";
private final KnoxSession session;
+ private String knoxToken;
private final String doAsUser;
@@ -53,8 +54,16 @@ public abstract class AbstractRequest<T> {
return session;
}
+ public AbstractRequest<T> knoxToken(String knoxToken) {
+ this.knoxToken = knoxToken;
+ return this;
+ }
+
protected CloseableHttpResponse execute(HttpRequest request ) throws IOException {
addHeaders(request, session.getHeaders());
+ if (knoxToken != null) {
+ request.addHeader("Authorization", "Bearer " + knoxToken);
+ }
return session.executeNow( request );
}
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenStateServiceStatisticsMBean.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenStateServiceStatisticsMBean.java
new file mode 100644
index 0000000..ce2537e
--- /dev/null
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenStateServiceStatisticsMBean.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.knox.gateway.services.security.token;
+
+import java.util.Map;
+
+public interface TokenStateServiceStatisticsMBean {
+
+ int getNumberOfTokensAdded();
+
+ int getNumberOfTokensRenewed();
+
+ Map<String, Integer> getKeystoreInteractions();
+
+ long getGatewayCredentialsFileSize();
+}
diff --git a/pom.xml b/pom.xml
index 0370b7a..373cd87 100644
--- a/pom.xml
+++ b/pom.xml
@@ -72,6 +72,7 @@
<module>gateway-spi</module>
<module>gateway-discovery-ambari</module>
<module>gateway-discovery-cm</module>
+ <module>gateway-performance-test</module>
<module>gateway-server</module>
<module>gateway-server-launcher</module>
<module>gateway-server-xforwarded-filter</module>
@@ -853,6 +854,11 @@
</dependency>
<dependency>
<groupId>org.apache.knox</groupId>
+ <artifactId>gateway-performance-test</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.knox</groupId>
<artifactId>gateway-spi</artifactId>
<version>${project.version}</version>
</dependency>