mirror of
https://github.com/NationalSecurityAgency/timely.git
synced 2026-11-09 17:36:01 -10:00
Compare commits
37
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d35ae08188 | ||
|
|
e448657afb | ||
|
|
1dcd893711 | ||
|
|
409f5375d1 | ||
|
|
c5df8139ca | ||
|
|
dcd23d4fc8 | ||
|
|
cc7326fc19 | ||
|
|
0d2b6e61c8 | ||
|
|
b7cd8765c5 | ||
|
|
a9d27d5b1b | ||
|
|
7d67eb218d | ||
|
|
5fb7e3dd2b | ||
|
|
5a89861cec | ||
|
|
a3bc9a01ee | ||
|
|
dba494b06d | ||
|
|
5826884c3c | ||
|
|
f94c731bbc | ||
|
|
4e042bcb94 | ||
|
|
ab7f499134 | ||
|
|
c098f9f3e4 | ||
|
|
94cfcbc0c4 | ||
|
|
674e1b0ab5 | ||
|
|
4eedb84547 | ||
|
|
bef86eac37 | ||
|
|
27bb16e403 | ||
|
|
a95d2d7ea1 | ||
|
|
f751175190 | ||
|
|
648199f330 | ||
|
|
fcfc3f7bc0 | ||
|
|
200bc39902 | ||
|
|
f854c9e394 | ||
|
|
73ddae1f8d | ||
|
|
cb7184503e | ||
|
|
3a357b50ef | ||
|
|
689aee0678 | ||
|
|
32f6b9d4c9 | ||
|
|
512f318387 |
No files matched your search
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
<version>1.2.4</version>
|
||||
</parent>
|
||||
<artifactId>timely-analytics</artifactId>
|
||||
<!--
|
||||
|
||||
@@ -66,7 +66,7 @@ public class SubscriptionSource extends RichSourceFunction<MetricResponse> imple
|
||||
LOG.info("Opening summarization job.");
|
||||
super.open(parameters);
|
||||
client = new WebSocketSubscriptionClient(jp.getTimelyHostname(), jp.getTimelyHttpsPort(), jp.getTimelyWssPort(),
|
||||
jp.isDoLogin(), jp.getTimelyUsername(), jp.getTimelyPassword(), jp.getKeyStoreFile(),
|
||||
jp.isClientAuth(), jp.isDoLogin(), jp.getTimelyUsername(), jp.getTimelyPassword(), jp.getKeyStoreFile(),
|
||||
jp.getKeyStoreType(), jp.getKeyStorePass(), jp.getTrustStoreFile(), jp.getTrustStoreType(),
|
||||
jp.getTrustStorePass(), jp.isHostVerificationEnabled(), jp.getBufferSize());
|
||||
}
|
||||
@@ -96,7 +96,7 @@ public class SubscriptionSource extends RichSourceFunction<MetricResponse> imple
|
||||
|
||||
@Override
|
||||
public void onOpen(Session session, EndpointConfig config) {
|
||||
session.addMessageHandler(new MessageHandler.Whole<String>() {
|
||||
session.addMessageHandler(String.class, new MessageHandler.Whole<String>() {
|
||||
|
||||
private final SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd-HH");
|
||||
private long lastWatermarkTime = 0L;
|
||||
|
||||
@@ -18,6 +18,7 @@ public class SummarizationJobParameters extends GlobalJobParameters implements S
|
||||
private final int timelyTcpPort;
|
||||
private final int timelyHttpsPort;
|
||||
private final int timelyWssPort;
|
||||
private final boolean clientAuth;
|
||||
private final boolean doLogin;
|
||||
private final String timelyUsername;
|
||||
private final String timelyPassword;
|
||||
@@ -40,6 +41,7 @@ public class SummarizationJobParameters extends GlobalJobParameters implements S
|
||||
timelyTcpPort = params.getInt("timelyTcpPort", 4241);
|
||||
timelyHttpsPort = params.getInt("timelyHttpsPort", 4242);
|
||||
timelyWssPort = params.getInt("timelyWssPort", 4243);
|
||||
clientAuth = params.getBoolean("clientAuth", false);
|
||||
doLogin = params.getBoolean("doLogin", false);
|
||||
timelyUsername = params.get("timelyUsername", null);
|
||||
timelyPassword = params.get("timelyPassword", null);
|
||||
@@ -69,6 +71,7 @@ public class SummarizationJobParameters extends GlobalJobParameters implements S
|
||||
timelyTcpPort = conf.getInteger("timelyTcpPort", 4241);
|
||||
timelyHttpsPort = conf.getInteger("timelyHttpsPort", 4242);
|
||||
timelyWssPort = conf.getInteger("timelyWssPort", 4243);
|
||||
clientAuth = conf.getBoolean("clientAuth", false);
|
||||
doLogin = conf.getBoolean("doLogin", false);
|
||||
timelyUsername = conf.getString("timelyUsername", null);
|
||||
timelyPassword = conf.getString("timelyPassword", null);
|
||||
@@ -112,6 +115,10 @@ public class SummarizationJobParameters extends GlobalJobParameters implements S
|
||||
return timelyWssPort;
|
||||
}
|
||||
|
||||
public boolean isClientAuth() {
|
||||
return clientAuth;
|
||||
}
|
||||
|
||||
public boolean isDoLogin() {
|
||||
return doLogin;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash -vx
|
||||
#!/bin/bash
|
||||
|
||||
if [[ `uname` == "Darwin" ]]; then
|
||||
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
|
||||
@@ -8,11 +8,6 @@ else
|
||||
TCNATIVE_SUFFIX="so"
|
||||
fi
|
||||
|
||||
# netty tcnative file reference
|
||||
#netty-tcnative-1.1.33.Fork18-linux-x86_64-fedora.jar -> libnetty-tcnative.so
|
||||
#netty-tcnative-1.1.33.Fork18-linux-x86_64.jar -> libnetty-tcnative.so
|
||||
#netty-tcnative-1.1.33.Fork18-osx-x86_64.jar -> libnetty-tcnative.jnilib
|
||||
|
||||
THIS_DIR="${THIS_SCRIPT%/*}"
|
||||
NATIVE_DIR="${THIS_DIR}/META-INF/native"
|
||||
BASE_DIR=${THIS_DIR}/..
|
||||
@@ -32,14 +27,15 @@ fi
|
||||
mkdir -p ${NATIVE_DIR}
|
||||
|
||||
pushd ${BASE_DIR}/bin
|
||||
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative*.jar META-INF/native/libnetty_tcnative.${NATIVE_SUFFIX}
|
||||
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-transport-native-epoll*.jar META-INF/native/libnetty_transport_native_epoll_x86_64.${NATIVE_SUFFIX}
|
||||
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative-boringssl-static*.jar META-INF/native/libnetty_tcnative_linux_x86_64.${TCNATIVE_SUFFIX}
|
||||
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-all*.jar META-INF/native/libnetty_transport_native_epoll_x86_64.${TCNATIVE_SUFFIX}
|
||||
popd
|
||||
|
||||
export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*"
|
||||
JVM_ARGS="-Xmx1G -Xms1G -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS}"
|
||||
export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*:${HADOOP_CONF_DIR}"
|
||||
JVM_ARGS="-Xmx1G -Xms1G -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS} -Dlog4j.configurationFile=${CONF_DIR}/log4j2-spring.xml"
|
||||
JVM_ARGS="${JVM_ARGS} -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"
|
||||
JVM_ARGS="${JVM_ARGS} -Djava.library.path=${NATIVE_DIR}"
|
||||
|
||||
#JVM_ARGS="${JVM_ARGS} -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"
|
||||
|
||||
echo "$JAVA_HOME/bin/java ${JVM_ARGS} timely.balancer.Balancer --spring.config.name=timely --spring.profiles.active=balancer"
|
||||
|
||||
@@ -22,5 +22,8 @@
|
||||
<AppenderRef ref="Console" level="warn" />
|
||||
<AppenderRef ref="Rolling" level="trace" />
|
||||
</Root>
|
||||
<Logger name="timely" additivity="false">
|
||||
<AppenderRef ref="Console" level="debug" />
|
||||
</Logger>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
+39
-17
@@ -11,21 +11,33 @@
|
||||
|
||||
<beans:bean id="x509PrincipalExtractor" class="org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor">
|
||||
<beans:property name="subjectDnRegex" value="CN=(.*?)(?:,|$)"/>
|
||||
</beans:bean>
|
||||
</beans:bean>
|
||||
|
||||
<beans:bean id="timelyUserDetailsService" class="timely.auth.UserDetailsService">
|
||||
<beans:property name="users">
|
||||
<util:map key-type="java.lang.String">
|
||||
<beans:entry key="example.com">
|
||||
<util:list>
|
||||
<beans:value>D</beans:value>
|
||||
<beans:value>E</beans:value>
|
||||
<beans:value>F</beans:value>
|
||||
</util:list>
|
||||
</beans:entry>
|
||||
</util:map>
|
||||
</beans:property>
|
||||
</beans:bean>
|
||||
<beans:bean id="timelyUserDetailsService" class="timely.auth.UserDetailsService">
|
||||
<beans:property name="users">
|
||||
<util:list>
|
||||
<beans:bean class="timely.auth.TimelyUser">
|
||||
<beans:constructor-arg name="subjectDn">
|
||||
<beans:value>CN=example.com</beans:value>
|
||||
</beans:constructor-arg>
|
||||
<beans:constructor-arg name="auths">
|
||||
<util:list>
|
||||
<beans:value>D</beans:value>
|
||||
<beans:value>E</beans:value>
|
||||
<beans:value>F</beans:value>
|
||||
</util:list>
|
||||
</beans:constructor-arg>
|
||||
<beans:constructor-arg name="roles">
|
||||
<util:list>
|
||||
<beans:value>G</beans:value>
|
||||
<beans:value>H</beans:value>
|
||||
<beans:value>I</beans:value>
|
||||
</util:list>
|
||||
</beans:constructor-arg>
|
||||
</beans:bean>
|
||||
</util:list>
|
||||
</beans:property>
|
||||
</beans:bean>
|
||||
|
||||
<beans:bean id="x509AuthorizationProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
|
||||
<beans:property name="preAuthenticatedUserDetailsService" ref="timelyUserDetailsService" />
|
||||
@@ -34,9 +46,12 @@
|
||||
<user-service id="usernamePasswordService">
|
||||
<user name="test" password="test1" authorities="A,B,C" />
|
||||
</user-service>
|
||||
|
||||
<beans:bean id="UserNamePasswordAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
|
||||
|
||||
<beans:bean id="noOpPasswordEncoder" class="org.springframework.security.crypto.password.NoOpPasswordEncoder"/>
|
||||
|
||||
<beans:bean id="UserNamePasswordAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
|
||||
<beans:property name="userDetailsService" ref="usernamePasswordService"/>
|
||||
<beans:property name="passwordEncoder" ref="noOpPasswordEncoder"/>
|
||||
</beans:bean>
|
||||
|
||||
<authentication-manager alias="authenticationManager">
|
||||
@@ -45,5 +60,12 @@
|
||||
<!-- Basic authorization provider, validates provided username and password -->
|
||||
<authentication-provider ref="UserNamePasswordAuthenticationProvider" />
|
||||
</authentication-manager>
|
||||
|
||||
|
||||
<util:list id="requiredRoles" value-type="java.lang.String">
|
||||
<beans:value></beans:value>
|
||||
</util:list>
|
||||
|
||||
<util:list id="requiredAuths" value-type="java.lang.String">
|
||||
<beans:value></beans:value>
|
||||
</util:list>
|
||||
</beans:beans>
|
||||
@@ -8,12 +8,15 @@ timely-balancer:
|
||||
assignmentFile: /timely/balancer/assignments.csv
|
||||
security:
|
||||
session-max-age: 86400
|
||||
allow-anonymous-access: true
|
||||
cache-expiration-minutes: 60
|
||||
cache-refresh-minutes: 5
|
||||
allow-anonymous-http-access: true
|
||||
allow-anonymous-ws-access: true
|
||||
serverSsl:
|
||||
certificate-file:
|
||||
key-file:
|
||||
#key-password:
|
||||
use-generated-keypair: false
|
||||
use-generated-keypair: true
|
||||
trust-store-file:
|
||||
use-openssl: true
|
||||
#use-ciphers:
|
||||
@@ -31,7 +34,7 @@ timely-balancer:
|
||||
nonCachedMetrics:
|
||||
server:
|
||||
ip: 127.0.0.1
|
||||
tcp-port: 4242
|
||||
tcpPort: 4242
|
||||
numTcpPools: 1
|
||||
tcpBufferSize: 100
|
||||
tcpClientPool:
|
||||
@@ -51,7 +54,7 @@ timely-balancer:
|
||||
jmxEnabled: true
|
||||
testOnBorrow: false
|
||||
testOnReturn: false
|
||||
udp-port: 4245
|
||||
udpPort: 4245
|
||||
udpClientPool:
|
||||
minIdlePerKey: 0
|
||||
maxIdlePerKey: 40
|
||||
|
||||
+3
-43
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
<version>1.2.4</version>
|
||||
</parent>
|
||||
<artifactId>timely-balancer</artifactId>
|
||||
<dependencies>
|
||||
@@ -161,9 +161,8 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-tcnative</artifactId>
|
||||
<artifactId>netty-tcnative-boringssl-static</artifactId>
|
||||
<version>${version.netty-tcnative}</version>
|
||||
<classifier>${os.detected.classifier}</classifier>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -201,11 +200,6 @@
|
||||
<artifactId>spring-boot-test-autoconfigure</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely-common</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
@@ -252,9 +246,8 @@
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-tcnative</artifactId>
|
||||
<artifactId>netty-tcnative-boringssl-static</artifactId>
|
||||
<version>${version.netty-tcnative}</version>
|
||||
<classifier>${os.detected.classifier}</classifier>
|
||||
<type>jar</type>
|
||||
<overWrite>false</overWrite>
|
||||
<outputDirectory>${project.build.directory}/native</outputDirectory>
|
||||
@@ -296,37 +289,4 @@
|
||||
</extension>
|
||||
</extensions>
|
||||
</build>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>native-epoll-linux</id>
|
||||
<activation>
|
||||
<os>
|
||||
<family>!mac</family>
|
||||
</os>
|
||||
</activation>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-transport-native-epoll</artifactId>
|
||||
<version>${version.netty}</version>
|
||||
<classifier>linux-x86_64</classifier>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>native-epoll</id>
|
||||
<activation>
|
||||
<os>
|
||||
<family>mac</family>
|
||||
</os>
|
||||
</activation>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-transport-native-epoll</artifactId>
|
||||
<version>${version.netty}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
Vendored
+1
-1
@@ -33,7 +33,7 @@
|
||||
<directory>${project.basedir}/bin</directory>
|
||||
<outputDirectory>bin</outputDirectory>
|
||||
<includes>
|
||||
<include>*</include>
|
||||
<include>**/*</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package timely.balancer;
|
||||
|
||||
import static timely.Server.SERVICE_DISCOVERY_PATH;
|
||||
import static timely.store.cache.DataStoreCache.NON_CACHED_METRICS;
|
||||
import static timely.store.cache.DataStoreCache.NON_CACHED_METRICS_LOCK_PATH;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -52,10 +56,18 @@ import io.netty.handler.stream.ChunkedWriteHandler;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
import io.netty.util.NettyRuntime;
|
||||
import io.netty.util.internal.SystemPropertyUtil;
|
||||
import org.apache.curator.RetryPolicy;
|
||||
import org.apache.curator.framework.CuratorFramework;
|
||||
import org.apache.curator.framework.CuratorFrameworkFactory;
|
||||
import org.apache.curator.retry.RetryForever;
|
||||
import org.apache.zookeeper.CreateMode;
|
||||
import org.apache.zookeeper.data.Stat;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import timely.auth.AuthCache;
|
||||
import timely.balancer.configuration.BalancerConfiguration;
|
||||
import timely.balancer.configuration.ClientSsl;
|
||||
import timely.balancer.configuration.SpringBootstrap;
|
||||
@@ -81,7 +93,7 @@ import timely.netty.tcp.TcpDecoder;
|
||||
import timely.netty.tcp.TcpVersionHandler;
|
||||
import timely.netty.udp.UdpDecoder;
|
||||
import timely.netty.udp.UdpPacketToByteBuf;
|
||||
import timely.netty.websocket.WebSocketHttpCookieHandler;
|
||||
import timely.netty.websocket.WebSocketFullRequestHandler;
|
||||
import timely.netty.websocket.WebSocketRequestDecoder;
|
||||
import timely.netty.websocket.subscription.WSTimelyExceptionHandler;
|
||||
|
||||
@@ -115,9 +127,13 @@ public class Balancer {
|
||||
protected Channel httpChannelHandle = null;
|
||||
protected Channel wsChannelHandle = null;
|
||||
protected List<Channel> udpChannelHandleList = new ArrayList<>();
|
||||
private CuratorFramework curatorFramework;
|
||||
protected volatile boolean shutdown = false;
|
||||
private static final int DEFAULT_EVENT_LOOP_THREADS;
|
||||
|
||||
private String[] zkPaths = new String[] { LEADER_LATCH_PATH, ASSIGNMENTS_LAST_UPDATED_PATH, ASSIGNMENTS_LOCK_PATH,
|
||||
SERVICE_DISCOVERY_PATH, NON_CACHED_METRICS, NON_CACHED_METRICS_LOCK_PATH };
|
||||
|
||||
static {
|
||||
DEFAULT_EVENT_LOOP_THREADS = Math.max(1,
|
||||
SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
|
||||
@@ -128,7 +144,7 @@ public class Balancer {
|
||||
}
|
||||
|
||||
protected static ConfigurableApplicationContext initializeConfiguration(String[] args) {
|
||||
return new SpringApplicationBuilder(SpringBootstrap.class).web(false).run(args);
|
||||
return new SpringApplicationBuilder(SpringBootstrap.class).web(WebApplicationType.NONE).run(args);
|
||||
}
|
||||
|
||||
private void shutdownHook() {
|
||||
@@ -239,6 +255,10 @@ public class Balancer {
|
||||
LOG.info("Closing applicationContext");
|
||||
applicationContext.close();
|
||||
}
|
||||
|
||||
if (curatorFramework != null) {
|
||||
curatorFramework.close();
|
||||
}
|
||||
this.shutdown = true;
|
||||
LOG.info("Server shut down.");
|
||||
}
|
||||
@@ -320,8 +340,31 @@ public class Balancer {
|
||||
ssl.getKeyFile(), ssl.getKeyType(), ssl.getKeyPassword());
|
||||
}
|
||||
|
||||
private void ensureZkPaths(CuratorFramework curatorFramework, String[] paths) {
|
||||
for (String s : paths) {
|
||||
try {
|
||||
Stat stat = curatorFramework.checkExists().forPath(s);
|
||||
if (stat == null) {
|
||||
curatorFramework.create().creatingParentContainersIfNeeded().withMode(CreateMode.PERSISTENT)
|
||||
.forPath(s);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.info(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void run() throws Exception {
|
||||
|
||||
// start curator framework
|
||||
RetryPolicy retryPolicy = new RetryForever(1000);
|
||||
curatorFramework = CuratorFrameworkFactory.newClient(balancerConfig.getZooKeeper().getServers(), 30000, 1000,
|
||||
retryPolicy);
|
||||
curatorFramework.start();
|
||||
ensureZkPaths(curatorFramework, zkPaths);
|
||||
|
||||
AuthCache.configure(balancerConfig.getSecurity());
|
||||
final boolean useEpoll = useEpoll();
|
||||
Class<? extends ServerSocketChannel> channelClass;
|
||||
Class<? extends Channel> datagramChannelClass;
|
||||
@@ -355,7 +398,7 @@ public class Balancer {
|
||||
tcpClientPools.add(new TcpClientPool(this.balancerConfig));
|
||||
}
|
||||
HealthChecker healthChecker = new HealthChecker(this.balancerConfig, tcpClientPools.get(0));
|
||||
this.metricResolver = new BalancedMetricResolver(this.balancerConfig, healthChecker);
|
||||
this.metricResolver = new BalancedMetricResolver(curatorFramework, this.balancerConfig, healthChecker);
|
||||
|
||||
final ServerBootstrap tcpServer = new ServerBootstrap();
|
||||
tcpServer.group(tcpBossGroup, tcpWorkerGroup);
|
||||
@@ -387,7 +430,8 @@ public class Balancer {
|
||||
httpServer.group(httpBossGroup, httpWorkerGroup);
|
||||
httpServer.channel(channelClass);
|
||||
httpServer.handler(new LoggingHandler());
|
||||
HttpClientPool httpClientPool = new HttpClientPool(balancerConfig, clientSSLContext);
|
||||
HttpClientPool httpClientPool = new HttpClientPool(balancerConfig.getSecurity(), balancerConfig.getHttp(),
|
||||
clientSSLContext);
|
||||
httpServer.childHandler(setupHttpChannel(balancerConfig, sslCtx, metricResolver, httpClientPool));
|
||||
httpServer.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
|
||||
httpServer.option(ChannelOption.SO_BACKLOG, 128);
|
||||
@@ -467,15 +511,14 @@ public class Balancer {
|
||||
ch.pipeline().addLast("decoder", new HttpRequestDecoder());
|
||||
ch.pipeline().addLast("compressor", new HttpContentCompressor());
|
||||
ch.pipeline().addLast("decompressor", new HttpContentDecompressor());
|
||||
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(8192));
|
||||
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
|
||||
ch.pipeline().addLast("chunker", new ChunkedWriteHandler());
|
||||
ch.pipeline().addLast("queryDecoder", new timely.netty.http.HttpRequestDecoder(
|
||||
balancerConfig.getSecurity(), balancerConfig.getHttp()));
|
||||
ch.pipeline().addLast("fileServer", new HttpStaticFileServerHandler());
|
||||
ch.pipeline().addLast("login",
|
||||
new X509LoginRequestHandler(balancerConfig.getSecurity(), balancerConfig.getHttp()));
|
||||
ch.pipeline().addLast("httpRelay",
|
||||
new HttpRelayHandler(balancerConfig, metricResolver, httpClientPool));
|
||||
ch.pipeline().addLast("httpRelay", new HttpRelayHandler(metricResolver, httpClientPool));
|
||||
ch.pipeline().addLast("error", new TimelyExceptionHandler());
|
||||
}
|
||||
};
|
||||
@@ -489,7 +532,7 @@ public class Balancer {
|
||||
ch.pipeline().addLast("logger", new LoggingHandler());
|
||||
ch.pipeline().addLast("packetDecoder", new UdpPacketToByteBuf());
|
||||
ch.pipeline().addLast("buffer", new MetricsBufferDecoder());
|
||||
ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(8192, true, Delimiters.lineDelimiter()));
|
||||
ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(65536, true, Delimiters.lineDelimiter()));
|
||||
ch.pipeline().addLast("putDecoder", new UdpDecoder());
|
||||
ch.pipeline().addLast("udpRelayHandler", new UdpRelayHandler(metricResolver, udpClientPool));
|
||||
}
|
||||
@@ -502,7 +545,7 @@ public class Balancer {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) throws Exception {
|
||||
ch.pipeline().addLast("buffer", new MetricsBufferDecoder());
|
||||
ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(8192, true, Delimiters.lineDelimiter()));
|
||||
ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(65536, true, Delimiters.lineDelimiter()));
|
||||
ch.pipeline().addLast("putDecoder", new TcpDecoder());
|
||||
ch.pipeline().addLast("tcpRelayHandler", new TcpRelayHandler(metricResolver, tcpClientPools));
|
||||
ch.pipeline().addLast("versionHandler", new TcpVersionHandler());
|
||||
@@ -518,13 +561,13 @@ public class Balancer {
|
||||
protected void initChannel(SocketChannel ch) throws Exception {
|
||||
ch.pipeline().addLast("ssl", sslCtx.newHandler(ch.alloc()));
|
||||
ch.pipeline().addLast("httpServer", new HttpServerCodec());
|
||||
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(8192));
|
||||
ch.pipeline().addLast("sessionExtractor", new WebSocketHttpCookieHandler(balancerConfig.getSecurity()));
|
||||
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
|
||||
ch.pipeline().addLast("sessionExtractor", new WebSocketFullRequestHandler());
|
||||
|
||||
ch.pipeline().addLast("idle-handler",
|
||||
new IdleStateHandler(balancerConfig.getWebsocket().getTimeout(), 0, 0));
|
||||
ch.pipeline().addLast("ws-protocol",
|
||||
new WebSocketServerProtocolHandler(WS_PATH, null, true, 65536, true));
|
||||
new WebSocketServerProtocolHandler(WS_PATH, null, true, 65536, false, true));
|
||||
ch.pipeline().addLast("wsDecoder", new WebSocketRequestDecoder(balancerConfig.getSecurity()));
|
||||
ch.pipeline().addLast("httpRelay", new WsRelayHandler(balancerConfig, metricResolver, wsClientPool));
|
||||
ch.pipeline().addLast("error", new WSTimelyExceptionHandler());
|
||||
|
||||
@@ -5,8 +5,10 @@ import javax.validation.Valid;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import timely.configuration.Cache;
|
||||
|
||||
@Validated
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "timely-balancer")
|
||||
public class BalancerConfiguration {
|
||||
|
||||
@@ -8,6 +8,8 @@ public class ClientSsl {
|
||||
private String trustStoreFile;
|
||||
private String trustStoreType;
|
||||
private String trustStorePassword;
|
||||
private boolean useClientCert = true;
|
||||
private boolean hostVerificationEnabled = true;
|
||||
|
||||
public String getKeyFile() {
|
||||
return keyFile;
|
||||
@@ -55,6 +57,21 @@ public class ClientSsl {
|
||||
|
||||
public void setTrustStorePassword(String trustStorePassword) {
|
||||
this.trustStorePassword = trustStorePassword;
|
||||
}
|
||||
|
||||
public void setUseClientCert(boolean useClientCert) {
|
||||
this.useClientCert = useClientCert;
|
||||
}
|
||||
|
||||
public boolean isUseClientCert() {
|
||||
return useClientCert;
|
||||
}
|
||||
|
||||
public void setHostVerificationEnabled(boolean hostVerificationEnabled) {
|
||||
this.hostVerificationEnabled = hostVerificationEnabled;
|
||||
}
|
||||
|
||||
public boolean isHostVerificationEnabled() {
|
||||
return hostVerificationEnabled;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package timely.balancer.connection;
|
||||
|
||||
import java.util.Timer;
|
||||
|
||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
@@ -8,8 +10,6 @@ import org.slf4j.LoggerFactory;
|
||||
import timely.balancer.ArrivalRate;
|
||||
import timely.balancer.configuration.BalancerConfiguration;
|
||||
|
||||
import java.util.Timer;
|
||||
|
||||
public class TimelyBalancedHost {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TimelyBalancedHost.class);
|
||||
|
||||
@@ -5,22 +5,25 @@ import javax.net.ssl.SSLContext;
|
||||
import org.apache.commons.pool2.KeyedPooledObjectFactory;
|
||||
import org.apache.commons.pool2.PooledObject;
|
||||
import org.apache.commons.pool2.impl.DefaultPooledObject;
|
||||
import org.apache.http.impl.client.BasicCookieStore;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import timely.balancer.configuration.BalancerSecurity;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
|
||||
public class HttpClientFactory implements KeyedPooledObjectFactory<TimelyBalancedHost, CloseableHttpClient> {
|
||||
|
||||
private final SSLContext sslContext;
|
||||
private final BalancerSecurity security;
|
||||
|
||||
public HttpClientFactory(SSLContext sslContext) {
|
||||
public HttpClientFactory(BalancerSecurity security, SSLContext sslContext) {
|
||||
this.security = security;
|
||||
this.sslContext = sslContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PooledObject<CloseableHttpClient> makeObject(TimelyBalancedHost k) throws Exception {
|
||||
BasicCookieStore cookieJar = new BasicCookieStore();
|
||||
return new DefaultPooledObject<>(timely.client.http.HttpClient.get(this.sslContext, cookieJar, false));
|
||||
// disable cookie management because we are sharing connections
|
||||
return new DefaultPooledObject<>(timely.client.http.HttpClient.get(this.sslContext, null,
|
||||
security.getClientSsl().isHostVerificationEnabled(), security.getClientSsl().isUseClientCert()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,14 +6,15 @@ import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.balancer.configuration.BalancerConfiguration;
|
||||
import timely.balancer.configuration.BalancerHttp;
|
||||
import timely.balancer.configuration.BalancerSecurity;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
|
||||
public class HttpClientPool extends GenericKeyedObjectPool<TimelyBalancedHost, CloseableHttpClient> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpClientPool.class);
|
||||
|
||||
public HttpClientPool(BalancerConfiguration config, SSLContext sslContext) {
|
||||
super(new HttpClientFactory(sslContext), config.getHttp().getHttpClientPool());
|
||||
public HttpClientPool(BalancerSecurity balancerSecurity, BalancerHttp http, SSLContext sslContext) {
|
||||
super(new HttpClientFactory(balancerSecurity, sslContext), http.getHttpClientPool());
|
||||
}
|
||||
}
|
||||
@@ -12,13 +12,11 @@ import timely.client.websocket.subscription.WebSocketSubscriptionClient;
|
||||
public class WsClientFactory implements KeyedPooledObjectFactory<TimelyBalancedHost, WebSocketSubscriptionClient> {
|
||||
|
||||
private final SSLContext sslContext;
|
||||
private final boolean doLogin;
|
||||
private BalancerConfiguration balancerConfig;
|
||||
|
||||
public WsClientFactory(BalancerConfiguration balancerConfig, SSLContext sslContext) {
|
||||
this.sslContext = sslContext;
|
||||
this.doLogin = balancerConfig.isLoginRequired();
|
||||
this.balancerConfig = balancerConfig;
|
||||
this.sslContext = sslContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -26,7 +24,9 @@ public class WsClientFactory implements KeyedPooledObjectFactory<TimelyBalancedH
|
||||
|
||||
int bufferSize = balancerConfig.getWebsocket().getSubscriptionBatchSize() * 500;
|
||||
WebSocketSubscriptionClient client = new WebSocketSubscriptionClient(sslContext, k.getHost(), k.getHttpPort(),
|
||||
k.getWsPort(), doLogin, "", "", false, bufferSize);
|
||||
k.getWsPort(), balancerConfig.getSecurity().getClientSsl().isUseClientCert(),
|
||||
balancerConfig.isLoginRequired(), "", "",
|
||||
balancerConfig.getSecurity().getClientSsl().isHostVerificationEnabled(), bufferSize);
|
||||
return new DefaultPooledObject<>(client);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package timely.balancer.netty.http;
|
||||
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
@@ -12,6 +11,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
@@ -33,26 +33,26 @@ import org.apache.http.message.BasicHeader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import timely.api.request.AuthenticatedRequest;
|
||||
import timely.api.request.MetricRequest;
|
||||
import timely.api.request.timeseries.HttpRequest;
|
||||
import timely.api.request.timeseries.QueryRequest;
|
||||
import timely.api.response.TimelyException;
|
||||
import timely.balancer.configuration.BalancerConfiguration;
|
||||
import timely.auth.util.HttpHeaderUtils;
|
||||
import timely.auth.util.ProxiedEntityUtils;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
import timely.balancer.connection.http.HttpClientPool;
|
||||
import timely.balancer.resolver.MetricResolver;
|
||||
import timely.netty.http.TimelyHttpHandler;
|
||||
import timely.netty.http.auth.TimelyAuthenticationToken;
|
||||
|
||||
public class HttpRelayHandler extends SimpleChannelInboundHandler<HttpRequest> implements TimelyHttpHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpRelayHandler.class);
|
||||
private final BalancerConfiguration balancerConf;
|
||||
private final HttpClientPool httpClientPool;
|
||||
private MetricResolver metricResolver;
|
||||
|
||||
public HttpRelayHandler(BalancerConfiguration balancerConf, MetricResolver metricResolver,
|
||||
HttpClientPool httpClientPool) {
|
||||
this.balancerConf = balancerConf;
|
||||
public HttpRelayHandler(MetricResolver metricResolver, HttpClientPool httpClientPool) {
|
||||
this.metricResolver = metricResolver;
|
||||
this.httpClientPool = httpClientPool;
|
||||
}
|
||||
@@ -86,13 +86,21 @@ public class HttpRelayHandler extends SimpleChannelInboundHandler<HttpRequest> i
|
||||
} else {
|
||||
k = metricResolver.getHostPortKey(null);
|
||||
}
|
||||
k = metricResolver.getHostPortKey(metric);
|
||||
client = httpClientPool.borrowObject(k);
|
||||
|
||||
List<Map.Entry<String, String>> headerList = request.headers().entries();
|
||||
List<Header> relayedHeaderList = new ArrayList<>();
|
||||
Multimap<String, String> headers = HttpHeaderUtils.toMultimap(request.headers());
|
||||
if (msg instanceof AuthenticatedRequest) {
|
||||
AuthenticatedRequest authenticatedRequest = (AuthenticatedRequest) msg;
|
||||
TimelyAuthenticationToken token = authenticatedRequest.getToken();
|
||||
if (token != null) {
|
||||
if (token.getClientCert() != null) {
|
||||
ProxiedEntityUtils.addProxyHeaders(headers, token.getClientCert());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> h : headerList) {
|
||||
List<Header> relayedHeaderList = new ArrayList<>();
|
||||
for (Map.Entry<String, String> h : headers.entries()) {
|
||||
if (!h.getKey().equals(CONTENT_LENGTH)) {
|
||||
relayedHeaderList.add(new BasicHeader(h.getKey(), h.getValue()));
|
||||
}
|
||||
@@ -143,7 +151,6 @@ public class HttpRelayHandler extends SimpleChannelInboundHandler<HttpRequest> i
|
||||
this.sendHttpError(ctx, new TimelyException(HttpResponseStatus.METHOD_NOT_ALLOWED.code(),
|
||||
"Method not allowed", "Method not allowed"));
|
||||
} else {
|
||||
// buf = JsonUtil.getObjectMapper().writeValueAsBytes(msg);
|
||||
ByteArrayOutputStream baos = new MyByteArrayOutputStream();
|
||||
StreamUtils.copy(relayedResponse.getEntity().getContent(), baos);
|
||||
|
||||
@@ -153,8 +160,6 @@ public class HttpRelayHandler extends SimpleChannelInboundHandler<HttpRequest> i
|
||||
for (Header h : relayedResponse.getAllHeaders()) {
|
||||
response.headers().add(h.getName(), h.getValue());
|
||||
}
|
||||
// response.headers().set(HttpHeaders.Names.CONTENT_TYPE,
|
||||
// Constants.JSON_TYPE);
|
||||
response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
|
||||
sendResponse(ctx, response);
|
||||
}
|
||||
@@ -197,13 +202,4 @@ public class HttpRelayHandler extends SimpleChannelInboundHandler<HttpRequest> i
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
String s = "https://127.0.0.1:4243/api/query?start=1356998400000&end=1528398304000&m=sum:rate{false,100,0}:sys.thermal.gauge{class=aws}";
|
||||
URI uri = URI.create(encodeURI(s));
|
||||
|
||||
System.out.println(uri.toString());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
package timely.balancer.netty.ws;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.websocket.CloseReason;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.MessageHandler;
|
||||
import javax.websocket.Session;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
|
||||
@@ -15,25 +19,48 @@ import io.netty.util.concurrent.ScheduledFuture;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.api.response.TimelyException;
|
||||
import timely.auth.util.ProxiedEntityUtils;
|
||||
import timely.client.websocket.ClientHandler;
|
||||
import timely.netty.http.auth.TimelyAuthenticationToken;
|
||||
|
||||
public class WsClientHandler extends ClientHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WsClientHandler.class);
|
||||
private final ChannelHandlerContext ctx;
|
||||
private final TimelyAuthenticationToken token;
|
||||
private ScheduledFuture<?> ping;
|
||||
|
||||
public WsClientHandler(ChannelHandlerContext ctx, int pingRate) {
|
||||
public WsClientHandler(ChannelHandlerContext ctx, TimelyAuthenticationToken token, int pingRate) {
|
||||
this.ctx = ctx;
|
||||
this.token = token;
|
||||
this.ping = this.ctx.executor().scheduleAtFixedRate(() -> {
|
||||
LOG.trace("Sending ping on channel {}", ctx.channel());
|
||||
ctx.writeAndFlush(new PingWebSocketFrame());
|
||||
}, pingRate, pingRate, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeRequest(Map<String, List<String>> headers) {
|
||||
if (token.getClientCert() != null) {
|
||||
Multimap<String, String> proxyRequestHeaders = HashMultimap.create();
|
||||
ProxiedEntityUtils.addProxyHeaders(proxyRequestHeaders, token.getClientCert());
|
||||
for (String s : proxyRequestHeaders.keySet()) {
|
||||
headers.put(s, new ArrayList(proxyRequestHeaders.get(s)));
|
||||
}
|
||||
}
|
||||
|
||||
Multimap<String, String> originalRequestHeaders = token.getHttpHeaders();
|
||||
for (String s : originalRequestHeaders.keySet()) {
|
||||
if (!headers.containsKey(s)) {
|
||||
// add pre-existing values if key does not exist in proxyRequest
|
||||
headers.put(s, new ArrayList(originalRequestHeaders.get(s)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(Session session, EndpointConfig config) {
|
||||
session.addMessageHandler((MessageHandler.Whole<String>) message -> {
|
||||
session.addMessageHandler(String.class, message -> {
|
||||
ctx.writeAndFlush(new TextWebSocketFrame(message));
|
||||
LOG.debug("Message received on Websocket session {}: {}", session.getId(), message);
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
|
||||
import com.google.common.collect.Multimap;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
@@ -24,6 +25,7 @@ import timely.api.request.subscription.CreateSubscription;
|
||||
import timely.api.request.subscription.RemoveSubscription;
|
||||
import timely.api.request.subscription.SubscriptionRequest;
|
||||
import timely.api.response.TimelyException;
|
||||
import timely.auth.util.ProxiedEntityUtils;
|
||||
import timely.balancer.configuration.BalancerConfiguration;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
import timely.balancer.connection.ws.WsClientPool;
|
||||
@@ -64,6 +66,10 @@ public class WsRelayHandler extends SimpleChannelInboundHandler<SubscriptionRequ
|
||||
String subscriptionId;
|
||||
try {
|
||||
String metric = null;
|
||||
|
||||
Multimap<String, String> headers = msg.getRequestHeaders();
|
||||
ProxiedEntityUtils.addProxyHeaders(headers, msg.getToken().getClientCert());
|
||||
|
||||
if (msg instanceof CreateSubscription) {
|
||||
CreateSubscription create = (CreateSubscription) msg;
|
||||
final String currentSubscriptionId = create.getSubscriptionId();
|
||||
@@ -118,7 +124,8 @@ public class WsRelayHandler extends SimpleChannelInboundHandler<SubscriptionRequ
|
||||
if (hostClientHolder == null) {
|
||||
k = metricResolver.getHostPortKey(metric);
|
||||
client = wsClientPool.borrowObject(k);
|
||||
client.open(new WsClientHandler(origContext, (balancerConfig.getWebsocket().getTimeout() / 2)));
|
||||
client.open(new WsClientHandler(origContext, add.getToken(),
|
||||
(balancerConfig.getWebsocket().getTimeout() / 2)));
|
||||
metricToClientMap.put(metric, new WsClientHolder(k, client));
|
||||
} else {
|
||||
client = hostClientHolder.getClient();
|
||||
|
||||
@@ -4,6 +4,8 @@ import static timely.Server.SERVICE_DISCOVERY_PATH;
|
||||
import static timely.balancer.Balancer.ASSIGNMENTS_LAST_UPDATED_PATH;
|
||||
import static timely.balancer.Balancer.ASSIGNMENTS_LOCK_PATH;
|
||||
import static timely.balancer.Balancer.LEADER_LATCH_PATH;
|
||||
import static timely.store.cache.DataStoreCache.NON_CACHED_METRICS;
|
||||
import static timely.store.cache.DataStoreCache.NON_CACHED_METRICS_LOCK_PATH;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
@@ -20,6 +22,8 @@ import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
@@ -27,10 +31,10 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import com.csvreader.CsvReader;
|
||||
import com.csvreader.CsvWriter;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.curator.RetryPolicy;
|
||||
import org.apache.commons.lang3.SerializationUtils;
|
||||
import org.apache.curator.framework.CuratorFramework;
|
||||
import org.apache.curator.framework.CuratorFrameworkFactory;
|
||||
import org.apache.curator.framework.recipes.atomic.DistributedAtomicLong;
|
||||
import org.apache.curator.framework.recipes.atomic.DistributedAtomicValue;
|
||||
import org.apache.curator.framework.recipes.cache.TreeCache;
|
||||
import org.apache.curator.framework.recipes.cache.TreeCacheEvent;
|
||||
import org.apache.curator.framework.recipes.cache.TreeCacheListener;
|
||||
@@ -38,7 +42,7 @@ import org.apache.curator.framework.recipes.leader.LeaderLatch;
|
||||
import org.apache.curator.framework.recipes.leader.LeaderLatchListener;
|
||||
import org.apache.curator.framework.recipes.locks.InterProcessReadWriteLock;
|
||||
import org.apache.curator.framework.state.ConnectionState;
|
||||
import org.apache.curator.retry.RetryUntilElapsed;
|
||||
import org.apache.curator.retry.RetryForever;
|
||||
import org.apache.curator.x.discovery.ServiceCache;
|
||||
import org.apache.curator.x.discovery.ServiceDiscovery;
|
||||
import org.apache.curator.x.discovery.ServiceDiscoveryBuilder;
|
||||
@@ -49,7 +53,7 @@ import org.apache.hadoop.fs.FSDataInputStream;
|
||||
import org.apache.hadoop.fs.FSDataOutputStream;
|
||||
import org.apache.hadoop.fs.FileSystem;
|
||||
import org.apache.hadoop.fs.Path;
|
||||
import org.apache.zookeeper.data.Stat;
|
||||
import org.apache.zookeeper.CreateMode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.ServerDetails;
|
||||
@@ -68,56 +72,50 @@ public class BalancedMetricResolver implements MetricResolver {
|
||||
private Random r = new Random();
|
||||
final private HealthChecker healthChecker;
|
||||
private Timer timer = new Timer("RebalanceTimer", true);
|
||||
private Timer arrivalRateTimer = new Timer("AriivalRateTimerResolver", true);
|
||||
private Timer arrivalRateTimer = new Timer("ArrivalRateTimerResolver", true);
|
||||
private int roundRobinCounter = 0;
|
||||
private Set<String> nonCachedMetrics = new HashSet<>();
|
||||
private ReentrantReadWriteLock nonCachedMetricsLock = new ReentrantReadWriteLock();
|
||||
private DistributedAtomicValue nonCachedMetricsIP;
|
||||
private ReentrantReadWriteLock nonCachedMetricsLocalLock = new ReentrantReadWriteLock();
|
||||
private InterProcessReadWriteLock nonCachedMetricsIPRWLock;
|
||||
private BalancerConfiguration balancerConfig;
|
||||
private CuratorFramework curatorFramework;
|
||||
private RetryPolicy retryPolicy = new RetryUntilElapsed(60000, 1000);
|
||||
private LeaderLatch leaderLatch;
|
||||
private AtomicBoolean isLeader = new AtomicBoolean(false);
|
||||
private InterProcessReadWriteLock assignmentsLock;
|
||||
private InterProcessReadWriteLock assignmentsIPRWLock;
|
||||
private DistributedAtomicLong assignmentsLastUpdatedInHdfs;
|
||||
private AtomicLong assignmentsLastUpdatedLocal = new AtomicLong(0);
|
||||
|
||||
private String[] zkPaths = new String[] { LEADER_LATCH_PATH, ASSIGNMENTS_LAST_UPDATED_PATH, ASSIGNMENTS_LOCK_PATH,
|
||||
SERVICE_DISCOVERY_PATH };
|
||||
|
||||
private enum BalanceType {
|
||||
HIGH_LOW, HIGH_AVG, AVG_LOW;
|
||||
HIGH_LOW, HIGH_AVG, AVG_LOW
|
||||
}
|
||||
|
||||
public BalancedMetricResolver(BalancerConfiguration balancerConfig, HealthChecker healthChecker) {
|
||||
public BalancedMetricResolver(CuratorFramework curatorFramework, BalancerConfiguration balancerConfig,
|
||||
HealthChecker healthChecker) {
|
||||
this.balancerConfig = balancerConfig;
|
||||
this.healthChecker = healthChecker;
|
||||
|
||||
// start curator framework
|
||||
curatorFramework = CuratorFrameworkFactory.newClient(balancerConfig.getZooKeeper().getServers(), 30000, 1000,
|
||||
retryPolicy);
|
||||
curatorFramework.start();
|
||||
ensureZkPaths(curatorFramework, zkPaths);
|
||||
assignmentsLock = new InterProcessReadWriteLock(curatorFramework, ASSIGNMENTS_LOCK_PATH);
|
||||
assignmentsIPRWLock = new InterProcessReadWriteLock(curatorFramework, ASSIGNMENTS_LOCK_PATH);
|
||||
testIPRWLock(curatorFramework, assignmentsIPRWLock, ASSIGNMENTS_LOCK_PATH);
|
||||
startLeaderLatch(curatorFramework);
|
||||
startServiceListener(curatorFramework);
|
||||
assignmentsLastUpdatedInHdfs = new DistributedAtomicLong(curatorFramework, ASSIGNMENTS_LAST_UPDATED_PATH,
|
||||
retryPolicy);
|
||||
new RetryForever(1000));
|
||||
|
||||
TreeCacheListener listener = new TreeCacheListener() {
|
||||
TreeCacheListener assignmentListener = new TreeCacheListener() {
|
||||
|
||||
@Override
|
||||
public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent event) throws Exception {
|
||||
LOG.info("Handling event {}. assignmentsLastUpdatedInHdfs:{}", event.getType().toString(),
|
||||
LOG.debug("Handling assignments event {}. assignmentsLastUpdatedInHdfs:{}", event.getType().toString(),
|
||||
new Date(assignmentsLastUpdatedInHdfs.get().postValue()));
|
||||
if (event.getType().equals(TreeCacheEvent.Type.NODE_UPDATED)) {
|
||||
long lastLocalUpdate = assignmentsLastUpdatedLocal.get();
|
||||
long lastHdfsUpdate = assignmentsLastUpdatedInHdfs.get().postValue();
|
||||
if (lastHdfsUpdate > lastLocalUpdate) {
|
||||
LOG.info("Reading assignments from hdfs lastHdfsUpdate ({}) > lastLocalUpdate ({})",
|
||||
LOG.debug("Reading assignments from hdfs lastHdfsUpdate ({}) > lastLocalUpdate ({})",
|
||||
new Date(lastHdfsUpdate), new Date(lastLocalUpdate));
|
||||
readAssignmentsFromHdfs();
|
||||
} else {
|
||||
LOG.info("Not reading assignments from hdfs lastHdfsUpdate ({}) <= lastLocalUpdate ({})",
|
||||
LOG.debug("Not reading assignments from hdfs lastHdfsUpdate ({}) <= lastLocalUpdate ({})",
|
||||
new Date(lastHdfsUpdate), new Date(lastLocalUpdate));
|
||||
}
|
||||
}
|
||||
@@ -125,20 +123,43 @@ public class BalancedMetricResolver implements MetricResolver {
|
||||
};
|
||||
|
||||
try {
|
||||
TreeCache treeCache = new TreeCache(curatorFramework, ASSIGNMENTS_LAST_UPDATED_PATH);
|
||||
treeCache.getListenable().addListener(listener);
|
||||
treeCache.start();
|
||||
TreeCache assignmentTreeCache = new TreeCache(curatorFramework, ASSIGNMENTS_LAST_UPDATED_PATH);
|
||||
assignmentTreeCache.getListenable().addListener(assignmentListener);
|
||||
assignmentTreeCache.start();
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
|
||||
nonCachedMetricsLock.writeLock().lock();
|
||||
nonCachedMetricsIPRWLock = new InterProcessReadWriteLock(curatorFramework, NON_CACHED_METRICS_LOCK_PATH);
|
||||
testIPRWLock(curatorFramework, nonCachedMetricsIPRWLock, NON_CACHED_METRICS_LOCK_PATH);
|
||||
nonCachedMetricsIP = new DistributedAtomicValue(curatorFramework, NON_CACHED_METRICS, new RetryForever(1000));
|
||||
TreeCacheListener nonCachedMetricsListener = new TreeCacheListener() {
|
||||
|
||||
@Override
|
||||
public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent event) throws Exception {
|
||||
if (event.getType().equals(TreeCacheEvent.Type.NODE_UPDATED)) {
|
||||
LOG.debug("Handling nonCachedMetricsIP event {}", event.getType().toString());
|
||||
readNonCachedMetricsIP();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
nonCachedMetrics.addAll(balancerConfig.getCache().getNonCachedMetrics());
|
||||
} finally {
|
||||
nonCachedMetricsLock.writeLock().unlock();
|
||||
TreeCache nonCachedMetricsTreeCache = new TreeCache(curatorFramework, NON_CACHED_METRICS);
|
||||
nonCachedMetricsTreeCache.getListenable().addListener(nonCachedMetricsListener);
|
||||
nonCachedMetricsTreeCache.start();
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
|
||||
nonCachedMetricsLocalLock.writeLock().lock();
|
||||
try {
|
||||
addNonCachedMetrics(balancerConfig.getCache().getNonCachedMetrics());
|
||||
} finally {
|
||||
nonCachedMetricsLocalLock.writeLock().unlock();
|
||||
}
|
||||
readNonCachedMetricsIP();
|
||||
|
||||
readAssignmentsFromHdfs();
|
||||
|
||||
timer.schedule(new TimerTask() {
|
||||
@@ -164,7 +185,7 @@ public class BalancedMetricResolver implements MetricResolver {
|
||||
long lastLocalUpdate = assignmentsLastUpdatedLocal.get();
|
||||
long lastHdfsUpdate = assignmentsLastUpdatedInHdfs.get().postValue();
|
||||
if (lastLocalUpdate > lastHdfsUpdate) {
|
||||
LOG.info("Writing assignments to hdfs lastLocalUpdate ({}) > lastHdfsUpdate ({})",
|
||||
LOG.debug("Writing assignments to hdfs lastLocalUpdate ({}) > lastHdfsUpdate ({})",
|
||||
new Date(lastLocalUpdate), new Date(lastHdfsUpdate));
|
||||
writeAssigmentsToHdfs();
|
||||
}
|
||||
@@ -176,17 +197,44 @@ public class BalancedMetricResolver implements MetricResolver {
|
||||
}, 10000, 60000);
|
||||
}
|
||||
|
||||
private void ensureZkPaths(CuratorFramework curatorFramework, String[] paths) {
|
||||
for (String s : paths) {
|
||||
try {
|
||||
Stat stat = curatorFramework.checkExists().forPath(s);
|
||||
if (stat == null) {
|
||||
curatorFramework.create().creatingParentContainersIfNeeded().forPath(s);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.info(e.getMessage());
|
||||
}
|
||||
private void readNonCachedMetricsIP() {
|
||||
|
||||
Set<String> currentNonCachedMetricsDistributed = new TreeSet<>();
|
||||
try {
|
||||
nonCachedMetricsIPRWLock.readLock().acquire();
|
||||
byte[] currentNonCachedMetricsDistributedBytes = nonCachedMetricsIP.get().postValue();
|
||||
if (currentNonCachedMetricsDistributedBytes != null) {
|
||||
try {
|
||||
currentNonCachedMetricsDistributed = SerializationUtils
|
||||
.deserialize(currentNonCachedMetricsDistributedBytes);
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage());
|
||||
currentNonCachedMetricsDistributed = new TreeSet<>();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
} finally {
|
||||
try {
|
||||
nonCachedMetricsIPRWLock.readLock().release();
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
nonCachedMetricsLocalLock.writeLock().lock();
|
||||
try {
|
||||
if (nonCachedMetrics.containsAll(currentNonCachedMetricsDistributed)) {
|
||||
LOG.debug("local nonCachedMetrics already contains {}", currentNonCachedMetricsDistributed);
|
||||
} else {
|
||||
currentNonCachedMetricsDistributed.removeAll(nonCachedMetrics);
|
||||
LOG.debug("Adding {} to local nonCachedMetrics", currentNonCachedMetricsDistributed);
|
||||
nonCachedMetrics.addAll(currentNonCachedMetricsDistributed);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
} finally {
|
||||
nonCachedMetricsLocalLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,21 +505,21 @@ public class BalancedMetricResolver implements MetricResolver {
|
||||
}
|
||||
|
||||
if (highHosts.isEmpty() && lowHosts.isEmpty()) {
|
||||
LOG.info("Host's arrival rates are within {} of the average", controlBandPercentage);
|
||||
LOG.debug("Host's arrival rates are within {} of the average", controlBandPercentage);
|
||||
} else if (!highHosts.isEmpty() && !lowHosts.isEmpty()) {
|
||||
LOG.info("begin rebalancing {}", BalanceType.HIGH_LOW);
|
||||
LOG.debug("begin rebalancing {}", BalanceType.HIGH_LOW);
|
||||
numReassigned = rebalance(highHosts, lowHosts, calculatedRates, averageArrivalRate,
|
||||
BalanceType.HIGH_LOW);
|
||||
LOG.info("end rebalancing {} - reassigned {}", BalanceType.HIGH_LOW, numReassigned);
|
||||
LOG.debug("end rebalancing {} - reassigned {}", BalanceType.HIGH_LOW, numReassigned);
|
||||
} else if (lowHosts.isEmpty()) {
|
||||
LOG.info("begin rebalancing {}", BalanceType.HIGH_AVG);
|
||||
LOG.debug("begin rebalancing {}", BalanceType.HIGH_AVG);
|
||||
numReassigned = rebalance(highHosts, avgHosts, calculatedRates, averageArrivalRate,
|
||||
BalanceType.HIGH_AVG);
|
||||
LOG.info("end rebalancing {} - reassigned {}", BalanceType.HIGH_AVG, numReassigned);
|
||||
LOG.debug("end rebalancing {} - reassigned {}", BalanceType.HIGH_AVG, numReassigned);
|
||||
} else {
|
||||
LOG.info("begin rebalancing {}", BalanceType.AVG_LOW);
|
||||
LOG.debug("begin rebalancing {}", BalanceType.AVG_LOW);
|
||||
numReassigned = rebalance(avgHosts, lowHosts, calculatedRates, averageArrivalRate, BalanceType.AVG_LOW);
|
||||
LOG.info("end rebalancing {} - reassigned {}", BalanceType.AVG_LOW, numReassigned);
|
||||
LOG.debug("end rebalancing {} - reassigned {}", BalanceType.AVG_LOW, numReassigned);
|
||||
}
|
||||
}
|
||||
return numReassigned;
|
||||
@@ -582,6 +630,70 @@ public class BalancedMetricResolver implements MetricResolver {
|
||||
return numReassigned;
|
||||
}
|
||||
|
||||
private void addNonCachedMetrics(Collection<String> nonCachedMetricsUpdate) {
|
||||
if (!nonCachedMetricsUpdate.isEmpty()) {
|
||||
try {
|
||||
LOG.debug("Adding {} to local nonCachedMetrics", nonCachedMetricsUpdate);
|
||||
nonCachedMetricsLocalLock.writeLock().lock();
|
||||
try {
|
||||
nonCachedMetrics.addAll(nonCachedMetricsUpdate);
|
||||
} finally {
|
||||
nonCachedMetricsLocalLock.writeLock().unlock();
|
||||
}
|
||||
|
||||
try {
|
||||
nonCachedMetricsIPRWLock.writeLock().acquire();
|
||||
byte[] currentNonCachedMetricsDistributedBytes = nonCachedMetricsIP.get().postValue();
|
||||
Set<String> currentNonCachedMetricsIP;
|
||||
if (currentNonCachedMetricsDistributedBytes == null) {
|
||||
currentNonCachedMetricsIP = new TreeSet<>();
|
||||
} else {
|
||||
currentNonCachedMetricsIP = SerializationUtils
|
||||
.deserialize(currentNonCachedMetricsDistributedBytes);
|
||||
}
|
||||
if (currentNonCachedMetricsIP.containsAll(nonCachedMetricsUpdate)) {
|
||||
LOG.debug("nonCachedMetricsIP already contains {}", nonCachedMetricsUpdate);
|
||||
} else {
|
||||
nonCachedMetricsUpdate.removeAll(currentNonCachedMetricsIP);
|
||||
LOG.debug("Adding {} to nonCachedMetricsIP", nonCachedMetricsUpdate);
|
||||
TreeSet<String> updateSet = new TreeSet<>();
|
||||
updateSet.addAll(currentNonCachedMetricsIP);
|
||||
updateSet.addAll(nonCachedMetricsUpdate);
|
||||
byte[] updateValue = SerializationUtils.serialize(updateSet);
|
||||
nonCachedMetricsIP.trySet(updateValue);
|
||||
if (!nonCachedMetricsIP.get().succeeded()) {
|
||||
nonCachedMetricsIP.forceSet(updateValue);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
nonCachedMetricsIPRWLock.writeLock().release();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void testIPRWLock(CuratorFramework curatorFramework, InterProcessReadWriteLock lock, String path) {
|
||||
try {
|
||||
lock.writeLock().acquire(10, TimeUnit.SECONDS);
|
||||
} catch (Exception e1) {
|
||||
try {
|
||||
curatorFramework.delete().deletingChildrenIfNeeded().forPath(path);
|
||||
curatorFramework.create().creatingParentContainersIfNeeded().withMode(CreateMode.PERSISTENT)
|
||||
.forPath(path);
|
||||
} catch (Exception e2) {
|
||||
LOG.info(e2.getMessage(), e2);
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
lock.writeLock().release();
|
||||
} catch (Exception e3) {
|
||||
LOG.error(e3.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldCache(String metricName) {
|
||||
|
||||
if (StringUtils.isBlank(metricName)) {
|
||||
@@ -589,6 +701,14 @@ public class BalancedMetricResolver implements MetricResolver {
|
||||
} else {
|
||||
balancerLock.readLock().lock();
|
||||
try {
|
||||
nonCachedMetricsLocalLock.readLock().lock();
|
||||
try {
|
||||
if (nonCachedMetrics.contains(metricName)) {
|
||||
return false;
|
||||
}
|
||||
} finally {
|
||||
nonCachedMetricsLocalLock.readLock().unlock();
|
||||
}
|
||||
if (metricToHostMap.containsKey(metricName)) {
|
||||
return true;
|
||||
}
|
||||
@@ -596,27 +716,17 @@ public class BalancedMetricResolver implements MetricResolver {
|
||||
balancerLock.readLock().unlock();
|
||||
}
|
||||
|
||||
nonCachedMetricsLock.readLock().lock();
|
||||
nonCachedMetricsLocalLock.writeLock().lock();
|
||||
try {
|
||||
if (nonCachedMetrics.contains(metricName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String r : nonCachedMetrics) {
|
||||
if (metricName.matches(r)) {
|
||||
LOG.info("Adding {} to list of non-cached metrics", metricName);
|
||||
nonCachedMetricsLock.readLock().unlock();
|
||||
nonCachedMetricsLock.writeLock().lock();
|
||||
nonCachedMetrics.add(metricName);
|
||||
LOG.debug("Adding {} to list of non-cached metrics", metricName);
|
||||
addNonCachedMetrics(Collections.singleton(metricName));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (nonCachedMetricsLock.isWriteLockedByCurrentThread()) {
|
||||
nonCachedMetricsLock.writeLock().unlock();
|
||||
} else {
|
||||
nonCachedMetricsLock.readLock().unlock();
|
||||
}
|
||||
nonCachedMetricsLocalLock.writeLock().unlock();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -659,7 +769,7 @@ public class BalancedMetricResolver implements MetricResolver {
|
||||
} else if (!tbh.isUp()) {
|
||||
TimelyBalancedHost oldTbh = tbh;
|
||||
tbh = getLeastUsedHost();
|
||||
LOG.info("rebalancing from host that is down: reassigning metric {} from server {}:{} to {}:{}",
|
||||
LOG.debug("rebalancing from host that is down: reassigning metric {} from server {}:{} to {}:{}",
|
||||
metric, oldTbh.getHost(), oldTbh.getTcpPort(), tbh.getHost(), tbh.getTcpPort());
|
||||
if (!balancerLock.isWriteLockedByCurrentThread()) {
|
||||
balancerLock.readLock().unlock();
|
||||
@@ -768,7 +878,8 @@ public class BalancedMetricResolver implements MetricResolver {
|
||||
|
||||
Map<String, TimelyBalancedHost> assignedMetricToHostMap = new TreeMap<>();
|
||||
try {
|
||||
assignmentsLock.readLock().acquire();
|
||||
assignmentsIPRWLock.readLock().acquire();
|
||||
balancerLock.writeLock().lock();
|
||||
Configuration configuration = new Configuration();
|
||||
FileSystem fs = FileSystem.get(configuration);
|
||||
Path assignmentFile = new Path(balancerConfig.getAssignmentFile());
|
||||
@@ -798,16 +909,24 @@ public class BalancedMetricResolver implements MetricResolver {
|
||||
}
|
||||
}
|
||||
|
||||
balancerLock.writeLock().lock();
|
||||
metricToHostMap.clear();
|
||||
for (Map.Entry<String, TimelyBalancedHost> e : assignedMetricToHostMap.entrySet()) {
|
||||
if (shouldCache(e.getKey())) {
|
||||
metricToHostMap.put(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
nonCachedMetricsLocalLock.readLock().lock();
|
||||
try {
|
||||
metricToHostMap.clear();
|
||||
for (Map.Entry<String, TimelyBalancedHost> e : assignedMetricToHostMap.entrySet()) {
|
||||
if (shouldCache(e.getKey())) {
|
||||
metricToHostMap.put(e.getKey(), e.getValue());
|
||||
Iterator<Map.Entry<String, TimelyBalancedHost>> itr = metricToHostMap.entrySet().iterator();
|
||||
while (itr.hasNext()) {
|
||||
Map.Entry<String, TimelyBalancedHost> e = itr.next();
|
||||
if (nonCachedMetrics.contains(e.getKey())) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
balancerLock.writeLock().unlock();
|
||||
nonCachedMetricsLocalLock.readLock().unlock();
|
||||
}
|
||||
assignmentsLastUpdatedLocal.set(assignmentsLastUpdatedInHdfs.get().postValue());
|
||||
LOG.info("Read {} assignments from hdfs lastHdfsUpdate = lastLocalUpdate ({})", metricToHostMap.size(),
|
||||
@@ -815,8 +934,9 @@ public class BalancedMetricResolver implements MetricResolver {
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
} finally {
|
||||
balancerLock.writeLock().unlock();
|
||||
try {
|
||||
assignmentsLock.readLock().release();
|
||||
assignmentsIPRWLock.readLock().release();
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
@@ -827,8 +947,9 @@ public class BalancedMetricResolver implements MetricResolver {
|
||||
|
||||
CsvWriter writer = null;
|
||||
try {
|
||||
assignmentsLock.writeLock().acquire();
|
||||
assignmentsIPRWLock.writeLock().acquire();
|
||||
balancerLock.readLock().lock();
|
||||
nonCachedMetricsLocalLock.readLock().lock();
|
||||
try {
|
||||
if (!metricToHostMap.isEmpty()) {
|
||||
Configuration configuration = new Configuration();
|
||||
@@ -845,12 +966,14 @@ public class BalancedMetricResolver implements MetricResolver {
|
||||
writer.write("tcpPort");
|
||||
writer.endRecord();
|
||||
for (Map.Entry<String, TimelyBalancedHost> e : metricToHostMap.entrySet()) {
|
||||
writer.write(e.getKey());
|
||||
writer.write(e.getValue().getHost());
|
||||
writer.write(Integer.toString(e.getValue().getTcpPort()));
|
||||
writer.endRecord();
|
||||
LOG.trace("Saving assigment: {} to {}:{}", e.getKey(), e.getValue().getHost(),
|
||||
e.getValue().getTcpPort());
|
||||
if (!nonCachedMetrics.contains(e.getKey())) {
|
||||
writer.write(e.getKey());
|
||||
writer.write(e.getValue().getHost());
|
||||
writer.write(Integer.toString(e.getValue().getTcpPort()));
|
||||
writer.endRecord();
|
||||
LOG.trace("Saving assigment: {} to {}:{}", e.getKey(), e.getValue().getHost(),
|
||||
e.getValue().getTcpPort());
|
||||
}
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
@@ -859,10 +982,11 @@ public class BalancedMetricResolver implements MetricResolver {
|
||||
if (!assignmentsLastUpdatedInHdfs.get().succeeded()) {
|
||||
assignmentsLastUpdatedInHdfs.forceSet(now);
|
||||
}
|
||||
LOG.info("Wrote {} assignments to hdfs lastHdfsUpdate = lastLocalUpdate ({})",
|
||||
LOG.debug("Wrote {} assignments to hdfs lastHdfsUpdate = lastLocalUpdate ({})",
|
||||
metricToHostMap.size(), new Date(assignmentsLastUpdatedLocal.get()));
|
||||
}
|
||||
} finally {
|
||||
nonCachedMetricsLocalLock.readLock().unlock();
|
||||
balancerLock.readLock().unlock();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -872,7 +996,7 @@ public class BalancedMetricResolver implements MetricResolver {
|
||||
writer.close();
|
||||
}
|
||||
try {
|
||||
assignmentsLock.writeLock().release();
|
||||
assignmentsIPRWLock.writeLock().release();
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
@@ -890,4 +1014,4 @@ public class BalancedMetricResolver implements MetricResolver {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
-2
@@ -4,12 +4,13 @@
|
||||
<parent>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
<version>1.2.4</version>
|
||||
</parent>
|
||||
<artifactId>timely-client</artifactId>
|
||||
<properties>
|
||||
<!-- PEP 440 compliant version gets substituted into src/main/python/template/setup.py -->
|
||||
<python.client.version>0.0.7.dev0</python.client.version>
|
||||
<!-- use .dev0 instead of -SNAPSHOT -->
|
||||
<python.client.version>1.2.4</python.client.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@@ -107,4 +108,43 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>python</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>python-dist</id>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
<configuration>
|
||||
<executable>python</executable>
|
||||
<workingDirectory>${project.basedir}/src/main/python</workingDirectory>
|
||||
<commandlineArgs>setup.py sdist --dist-dir ${project.build.directory}</commandlineArgs>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>python-clean</id>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
<configuration>
|
||||
<executable>rm</executable>
|
||||
<workingDirectory>${project.basedir}/src/main/python</workingDirectory>
|
||||
<commandlineArgs>-rf timely.egg-info setup.py</commandlineArgs>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
@@ -1,12 +1,13 @@
|
||||
package timely.client.http;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.apache.http.client.CookieStore;
|
||||
import org.apache.http.client.config.CookieSpecs;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
|
||||
import org.apache.http.conn.ssl.NoopHostnameVerifier;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
@@ -40,14 +41,31 @@ public class HttpClient {
|
||||
}
|
||||
|
||||
public static CloseableHttpClient get(SSLContext ssl, CookieStore cookieStore, boolean hostVerificationEnabled) {
|
||||
RequestConfig defaultRequestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build();
|
||||
return get(ssl, cookieStore, hostVerificationEnabled, false);
|
||||
}
|
||||
|
||||
HttpClientBuilder builder = HttpClients.custom().setSSLContext(ssl).setDefaultCookieStore(cookieStore)
|
||||
.setDefaultRequestConfig(defaultRequestConfig);
|
||||
public static CloseableHttpClient get(SSLContext ssl, CookieStore cookieStore, boolean hostVerificationEnabled,
|
||||
boolean clientAuth) {
|
||||
HostnameVerifier hostnameVerifier;
|
||||
if (hostVerificationEnabled) {
|
||||
builder.setSSLHostnameVerifier(new DefaultHostnameVerifier());
|
||||
hostnameVerifier = SSLConnectionSocketFactory.getDefaultHostnameVerifier();
|
||||
} else {
|
||||
builder.setSSLHostnameVerifier(new NoopHostnameVerifier());
|
||||
hostnameVerifier = new NoopHostnameVerifier();
|
||||
}
|
||||
RequestConfig defaultRequestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build();
|
||||
HttpClientBuilder builder;
|
||||
if (cookieStore == null) {
|
||||
builder = HttpClients.custom().setSSLContext(ssl).disableCookieManagement()
|
||||
.setDefaultRequestConfig(defaultRequestConfig).setSSLHostnameVerifier(hostnameVerifier);
|
||||
} else {
|
||||
builder = HttpClients.custom().setSSLContext(ssl).setDefaultCookieStore(cookieStore)
|
||||
.setDefaultRequestConfig(defaultRequestConfig).setSSLHostnameVerifier(hostnameVerifier);
|
||||
}
|
||||
|
||||
if (clientAuth) {
|
||||
SSLConnectionSocketFactory sslCSF = new SSLConnectionSocketFactory(ssl,
|
||||
new String[] { "TLSv1.1", "TLSv1.2" }, null, hostnameVerifier);
|
||||
builder.setSSLSocketFactory(sslCSF);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package timely.client.websocket;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.websocket.CloseReason;
|
||||
import javax.websocket.Endpoint;
|
||||
import javax.websocket.EndpointConfig;
|
||||
@@ -35,4 +38,6 @@ public abstract class ClientHandler extends Endpoint {
|
||||
LOG.error("Error occurred on Websocket session" + session.getId(), error);
|
||||
}
|
||||
|
||||
public void beforeRequest(Map<String, List<String>> headers) {
|
||||
}
|
||||
}
|
||||
@@ -14,13 +14,15 @@ import org.apache.http.cookie.Cookie;
|
||||
public class TimelyEndpointConfig implements ClientEndpointConfig {
|
||||
|
||||
private String sessionCookie = null;
|
||||
private final ClientHandler clientEndpoint;
|
||||
|
||||
private static final String FORMAT = "%s=%s;";
|
||||
|
||||
public TimelyEndpointConfig(Cookie sessionCookie) {
|
||||
public TimelyEndpointConfig(ClientHandler clientEndpoint, Cookie sessionCookie) {
|
||||
if (null != sessionCookie) {
|
||||
this.sessionCookie = String.format(FORMAT, sessionCookie.getName(), sessionCookie.getValue());
|
||||
}
|
||||
this.clientEndpoint = clientEndpoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,6 +60,7 @@ public class TimelyEndpointConfig implements ClientEndpointConfig {
|
||||
if (null != sessionCookie) {
|
||||
headers.put("Cookie", Collections.singletonList(sessionCookie));
|
||||
}
|
||||
TimelyEndpointConfig.this.clientEndpoint.beforeRequest(headers);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ public class WebSocketClient implements AutoCloseable {
|
||||
private final int timelyHttpsPort;
|
||||
private final int timelyWssPort;
|
||||
private final boolean doLogin;
|
||||
private final boolean clientAuth;
|
||||
private final String timelyUsername;
|
||||
private final String timelyPassword;
|
||||
private final boolean hostVerificationEnabled;
|
||||
@@ -51,13 +52,14 @@ public class WebSocketClient implements AutoCloseable {
|
||||
protected volatile boolean closed = true;
|
||||
|
||||
protected WebSocketClient(SSLContext ssl, String timelyHostname, int timelyHttpsPort, int timelyWssPort,
|
||||
boolean doLogin, String timelyUsername, String timelyPassword, boolean hostVerificationEnabled,
|
||||
int bufferSize) {
|
||||
boolean clientAuth, boolean doLogin, String timelyUsername, String timelyPassword,
|
||||
boolean hostVerificationEnabled, int bufferSize) {
|
||||
this.ssl = ssl;
|
||||
this.timelyHostname = timelyHostname;
|
||||
this.timelyHttpsPort = timelyHttpsPort;
|
||||
this.timelyWssPort = timelyWssPort;
|
||||
this.doLogin = doLogin;
|
||||
this.clientAuth = clientAuth;
|
||||
this.timelyUsername = timelyUsername;
|
||||
this.timelyPassword = timelyPassword;
|
||||
this.hostVerificationEnabled = hostVerificationEnabled;
|
||||
@@ -67,28 +69,28 @@ public class WebSocketClient implements AutoCloseable {
|
||||
Preconditions.checkNotNull(timelyHttpsPort, "%s must be supplied", "Timely HTTPS port");
|
||||
Preconditions.checkNotNull(timelyWssPort, "%s must be supplied", "Timely WSS port");
|
||||
|
||||
if (doLogin && ((StringUtils.isEmpty(timelyUsername) && !StringUtils.isEmpty(timelyPassword)
|
||||
|| (!StringUtils.isEmpty(timelyUsername) && StringUtils.isEmpty(timelyPassword))))) {
|
||||
throw new IllegalArgumentException("Both Timely username and password must be empty or non-empty");
|
||||
if (doLogin && !clientAuth) {
|
||||
if ((StringUtils.isEmpty(timelyUsername) && !StringUtils.isEmpty(timelyPassword)
|
||||
|| (!StringUtils.isEmpty(timelyUsername) && StringUtils.isEmpty(timelyPassword)))) {
|
||||
throw new IllegalArgumentException("Both Timely username and password must be empty or non-empty");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected WebSocketClient(String timelyHostname, int timelyHttpsPort, int timelyWssPort, boolean doLogin,
|
||||
String timelyUsername, String timelyPassword, String keyStoreFile, String keyStoreType, String keyStorePass,
|
||||
String trustStoreFile, String trustStoreType, String trustStorePass, boolean hostVerificationEnabled,
|
||||
int bufferSize) {
|
||||
protected WebSocketClient(String timelyHostname, int timelyHttpsPort, int timelyWssPort, boolean clientAuth,
|
||||
boolean doLogin, String timelyUsername, String timelyPassword, String keyStoreFile, String keyStoreType,
|
||||
String keyStorePass, String trustStoreFile, String trustStoreType, String trustStorePass,
|
||||
boolean hostVerificationEnabled, int bufferSize) {
|
||||
this(HttpClient.getSSLContext(trustStoreFile, trustStoreType, trustStorePass, keyStoreFile, keyStoreType,
|
||||
keyStorePass), timelyHostname, timelyHttpsPort, timelyWssPort, doLogin, timelyUsername, timelyPassword,
|
||||
hostVerificationEnabled, bufferSize);
|
||||
keyStorePass), timelyHostname, timelyHttpsPort, timelyWssPort, clientAuth, doLogin, timelyUsername,
|
||||
timelyPassword, hostVerificationEnabled, bufferSize);
|
||||
}
|
||||
|
||||
public void open(ClientHandler clientEndpoint) throws IOException, DeploymentException, URISyntaxException {
|
||||
|
||||
Cookie sessionCookie = null;
|
||||
if (doLogin) {
|
||||
BasicCookieStore cookieJar = new BasicCookieStore();
|
||||
try (CloseableHttpClient client = HttpClient.get(ssl, cookieJar, hostVerificationEnabled)) {
|
||||
try (CloseableHttpClient client = HttpClient.get(ssl, cookieJar, hostVerificationEnabled, clientAuth)) {
|
||||
|
||||
String target = "https://" + timelyHostname + ":" + timelyHttpsPort + "/login";
|
||||
|
||||
@@ -140,8 +142,8 @@ public class WebSocketClient implements AutoCloseable {
|
||||
webSocketClient.getProperties().put(ClientProperties.SSL_ENGINE_CONFIGURATOR, sslEngine);
|
||||
webSocketClient.getProperties().put(ClientProperties.INCOMING_BUFFER_SIZE, bufferSize);
|
||||
String wssPath = "wss://" + timelyHostname + ":" + timelyWssPort + "/websocket";
|
||||
session = webSocketClient.connectToServer(clientEndpoint, new TimelyEndpointConfig(sessionCookie),
|
||||
new URI(wssPath));
|
||||
session = webSocketClient.connectToServer(clientEndpoint,
|
||||
new TimelyEndpointConfig(clientEndpoint, sessionCookie), new URI(wssPath));
|
||||
|
||||
final ByteBuffer pingData = ByteBuffer.allocate(0);
|
||||
webSocketClient.getScheduledExecutorService().scheduleAtFixedRate(() -> {
|
||||
|
||||
+10
-10
@@ -27,21 +27,21 @@ public class WebSocketSubscriptionClient extends WebSocketClient {
|
||||
private final String subscriptionId;
|
||||
|
||||
public WebSocketSubscriptionClient(SSLContext ssl, String timelyHostname, int timelyHttpsPort, int timelyWssPort,
|
||||
boolean doLogin, String timelyUsername, String timelyPassword, boolean hostVerificationEnabled,
|
||||
int bufferSize) {
|
||||
super(ssl, timelyHostname, timelyHttpsPort, timelyWssPort, doLogin, timelyUsername, timelyPassword,
|
||||
boolean clientAuth, boolean doLogin, String timelyUsername, String timelyPassword,
|
||||
boolean hostVerificationEnabled, int bufferSize) {
|
||||
super(ssl, timelyHostname, timelyHttpsPort, timelyWssPort, clientAuth, doLogin, timelyUsername, timelyPassword,
|
||||
hostVerificationEnabled, bufferSize);
|
||||
subscriptionId = UUID.randomUUID().toString();
|
||||
LOG.trace("Created WebSocketClient with subscriptionId {}", this.subscriptionId);
|
||||
}
|
||||
|
||||
public WebSocketSubscriptionClient(String timelyHostname, int timelyHttpsPort, int timelyWssPort, boolean doLogin,
|
||||
String timelyUsername, String timelyPassword, String keyStoreFile, String keyStoreType, String keyStorePass,
|
||||
String trustStoreFile, String trustStoreType, String trustStorePass, boolean hostVerificationEnabled,
|
||||
int bufferSize) {
|
||||
super(timelyHostname, timelyHttpsPort, timelyWssPort, doLogin, timelyUsername, timelyPassword, keyStoreFile,
|
||||
keyStoreType, keyStorePass, trustStoreFile, trustStoreType, trustStorePass, hostVerificationEnabled,
|
||||
bufferSize);
|
||||
public WebSocketSubscriptionClient(String timelyHostname, int timelyHttpsPort, int timelyWssPort,
|
||||
boolean clientAuth, boolean doLogin, String timelyUsername, String timelyPassword, String keyStoreFile,
|
||||
String keyStoreType, String keyStorePass, String trustStoreFile, String trustStoreType,
|
||||
String trustStorePass, boolean hostVerificationEnabled, int bufferSize) {
|
||||
super(timelyHostname, timelyHttpsPort, timelyWssPort, clientAuth, doLogin, timelyUsername, timelyPassword,
|
||||
keyStoreFile, keyStoreType, keyStorePass, trustStoreFile, trustStoreType, trustStorePass,
|
||||
hostVerificationEnabled, bufferSize);
|
||||
subscriptionId = UUID.randomUUID().toString();
|
||||
LOG.trace("Created WebSocketClient with subscriptionId {}", this.subscriptionId);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ def pivot(df, metric, groupByColumn=None):
|
||||
return dataFrame
|
||||
|
||||
|
||||
def resample(df, sample, how='mean'):
|
||||
def resample(df, sample, how='mean', interpolate=True, fill=None):
|
||||
|
||||
dataFrame = pandas.DataFrame(df, copy=True)
|
||||
if dataFrame is not None:
|
||||
|
||||
@@ -2,7 +2,6 @@ import time
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from datetime import tzinfo
|
||||
from pandas.tslib import Timedelta
|
||||
import pandas
|
||||
|
||||
class TimeDateError(ValueError):
|
||||
|
||||
@@ -16,14 +16,15 @@ utc = UTC()
|
||||
class TimelyWebSocketClient(WebSocketClient):
|
||||
|
||||
def __init__(self, hostport, metric, tags, startTime, endTime, connect_timeout=WebSocketClient.DEFAULT_CONNECT_TIMEOUT,
|
||||
request_timeout=WebSocketClient.DEFAULT_REQUEST_TIMEOUT):
|
||||
request_timeout=WebSocketClient.DEFAULT_REQUEST_TIMEOUT, client_cert=None, client_key=None, ca_certs=None, validate_cert=False):
|
||||
|
||||
self.metric = metric
|
||||
self.tags = tags
|
||||
self.startTime = startTime
|
||||
self.endTime = endTime
|
||||
self.subscriptionId = random.randint(1000000000, 9999999999)
|
||||
WebSocketClient.__init__(self, hostport, '/websocket', {'metric' : metric}, connect_timeout, request_timeout)
|
||||
WebSocketClient.__init__(self, hostport, '/websocket', {'metric' : metric}, connect_timeout, request_timeout,
|
||||
client_cert=client_cert, client_key=client_key, ca_certs=ca_certs, validate_cert=validate_cert)
|
||||
|
||||
def _on_message(self, msg):
|
||||
# implement in subclass
|
||||
@@ -72,7 +73,8 @@ class TimelyMetric(TimelyWebSocketClient):
|
||||
client = None
|
||||
endtime = None
|
||||
|
||||
def __init__(self, hostport, metric, tags, beginStr, endStr, period, sample):
|
||||
def __init__(self, hostport, metric, tags, beginStr, endStr, period, sample,
|
||||
client_cert=None, client_key=None, ca_certs=None, validate_cert=False):
|
||||
self.series = pandas.Series()
|
||||
self.metric = metric
|
||||
self.tags = tags
|
||||
@@ -82,7 +84,8 @@ class TimelyMetric(TimelyWebSocketClient):
|
||||
self.data = []
|
||||
self.timeDateRange = TimeDateRange(beginStr, endStr, period)
|
||||
|
||||
TimelyWebSocketClient.__init__(self, hostport, metric, tags, self.timeDateRange.getBeginMs(), self.timeDateRange.getEndMs())
|
||||
TimelyWebSocketClient.__init__(self, hostport, metric, tags, self.timeDateRange.getBeginMs(), self.timeDateRange.getEndMs(),
|
||||
client_cert=client_cert, client_key=client_key, ca_certs=ca_certs, validate_cert=validate_cert)
|
||||
|
||||
def debugOn(self):
|
||||
self.debug = True
|
||||
|
||||
@@ -15,10 +15,14 @@ class WebSocketClient():
|
||||
APPLICATION_JSON = 'application/json'
|
||||
|
||||
def __init__(self, hostport, path, params, connect_timeout=DEFAULT_CONNECT_TIMEOUT,
|
||||
request_timeout=DEFAULT_REQUEST_TIMEOUT):
|
||||
request_timeout=DEFAULT_REQUEST_TIMEOUT, client_cert=None, client_key=None, ca_certs=None, validate_cert=False):
|
||||
|
||||
self.connect_timeout = connect_timeout
|
||||
self.request_timeout = request_timeout
|
||||
self.client_cert=client_cert
|
||||
self.client_key=client_key
|
||||
self.ca_certs=ca_certs
|
||||
self.validate_cert=validate_cert
|
||||
self.url = 'wss://' + hostport
|
||||
if (path is not None and len(path) > 0):
|
||||
if (path.startswith('/')):
|
||||
@@ -39,7 +43,8 @@ class WebSocketClient():
|
||||
"""Connect to the server.
|
||||
:param str url: server URL.
|
||||
"""
|
||||
request = websocket.httpclient.HTTPRequest(self.url, connect_timeout=self.connect_timeout, request_timeout=self.request_timeout, validate_cert=False)
|
||||
request = websocket.httpclient.HTTPRequest(self.url, connect_timeout=self.connect_timeout, request_timeout=self.request_timeout, validate_cert=self.validate_cert,
|
||||
client_cert=self.client_cert, client_key=self.client_key, ca_certs=self.ca_certs)
|
||||
websocket.websocket_connect(request, io_loop=ioloop, callback=self._connect_callback)
|
||||
|
||||
def send(self, data):
|
||||
|
||||
@@ -5,7 +5,9 @@ import os
|
||||
from timely.TimelyAnalyticConfiguration import TimelyAnalyticConfiguration
|
||||
|
||||
hostport = "127.0.0.1:5244"
|
||||
timelyMetric = timely.TimelyMetric.TimelyMetric(hostport, 'timely.keys.metric.inserted', None, None, None, '24 hours', None).fetch()
|
||||
client_cert = None
|
||||
client_key = None
|
||||
timelyMetric = timely.TimelyMetric.TimelyMetric(hostport, 'timely.keys.metric.inserted', None, None, None, '24 hours', None, client_cert=client_cert, client_key=client_key).fetch()
|
||||
|
||||
print(timelyMetric.getDataFrame())
|
||||
exit()
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
<version>1.2.4</version>
|
||||
</parent>
|
||||
<artifactId>collectd-nsq-plugin</artifactId>
|
||||
<dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
<version>1.2.4</version>
|
||||
</parent>
|
||||
<artifactId>collectd-plugin-parent</artifactId>
|
||||
<dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
<version>1.2.4</version>
|
||||
</parent>
|
||||
<artifactId>collectd-timely-plugin</artifactId>
|
||||
<dependencies>
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
<version>1.2.4</version>
|
||||
</parent>
|
||||
<artifactId>timely-common</artifactId>
|
||||
<dependencies>
|
||||
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ `uname` == "Darwin" ]]; then
|
||||
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
|
||||
TCNATIVE_SUFFIX="jnilib"
|
||||
else
|
||||
THIS_SCRIPT=`readlink -f $0`
|
||||
TCNATIVE_SUFFIX="so"
|
||||
fi
|
||||
|
||||
THIS_DIR="${THIS_SCRIPT%/*}"
|
||||
NATIVE_DIR="${THIS_DIR}/META-INF/native"
|
||||
BASE_DIR=${THIS_DIR}/..
|
||||
TMP_DIR="${BASE_DIR}/tmp"
|
||||
CONF_DIR="${BASE_DIR}/conf"
|
||||
LIB_DIR="${BASE_DIR}/lib"
|
||||
NUM_SERVER_THREADS=4
|
||||
|
||||
if [[ -e ${TMP_DIR} ]]; then
|
||||
rm -rf ${TMP_DIR}
|
||||
fi
|
||||
mkdir ${TMP_DIR}
|
||||
|
||||
if [[ -e ${NATIVE_DIR} ]]; then
|
||||
rm -rf ${NATIVE_DIR}
|
||||
fi
|
||||
mkdir -p ${NATIVE_DIR}
|
||||
|
||||
pushd ${BASE_DIR}/bin
|
||||
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative-boringssl-static*.jar META-INF/native/libnetty_tcnative_linux_x86_64.${TCNATIVE_SUFFIX}
|
||||
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-all*.jar META-INF/native/libnetty_transport_native_epoll_x86_64.${TCNATIVE_SUFFIX}
|
||||
popd
|
||||
|
||||
export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*:${HADOOP_CONF_DIR}"
|
||||
JVM_ARGS="-Xmx1G -Xms1G -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS} Dlog4j.configurationFile=${CONF_DIR}/log4j2-spring.xml"
|
||||
JVM_ARGS="${JVM_ARGS} -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"
|
||||
JVM_ARGS="${JVM_ARGS} -Djava.library.path=${NATIVE_DIR}"
|
||||
|
||||
#JVM_ARGS="${JVM_ARGS} -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"
|
||||
|
||||
echo "$JAVA_HOME/bin/java ${JVM_ARGS} timely.grafana.auth.GrafanaAuth --spring.config.name=grafana-auth"
|
||||
$JAVA_HOME/bin/java ${JVM_ARGS} timely.grafana.auth.GrafanaAuth --spring.config.name=grafana-auth
|
||||
@@ -0,0 +1,55 @@
|
||||
grafana-auth:
|
||||
grafana:
|
||||
host: localhost
|
||||
port: 3001
|
||||
security:
|
||||
session-max-age: 86400
|
||||
cache-expiration-minutes: 60
|
||||
cache-refresh-minutes: 5
|
||||
allow-anonymous-http-access: true
|
||||
allow-anonymous-ws-access: true
|
||||
serverSsl:
|
||||
certificate-file:
|
||||
key-file:
|
||||
#key-password:
|
||||
use-generated-keypair: true
|
||||
trust-store-file:
|
||||
use-openssl: true
|
||||
#use-ciphers:
|
||||
clientSsl:
|
||||
keyFile:
|
||||
keyType: pkcs12
|
||||
#keyPassword:
|
||||
trustStoreFile:
|
||||
trustStoreType: jks
|
||||
#trustStorePassword:
|
||||
http:
|
||||
host: localhost
|
||||
ip: 127.0.0.1
|
||||
port: 3000
|
||||
redirect-path: /secure-me
|
||||
strict-transport-max-age: 608400
|
||||
cors:
|
||||
allow-any-origin: false
|
||||
allow-null-origin: false
|
||||
allow-credentials: true
|
||||
allowed-origins:
|
||||
allowed-methods: DELETE,GET,HEAD,OPTIONS,PUT,POST
|
||||
allowed-headers: content-type
|
||||
httpClientPool:
|
||||
minIdlePerKey: 0
|
||||
maxIdlePerKey: 40
|
||||
maxTotalPerKey: 40
|
||||
maxTotal: -1
|
||||
lifo: true
|
||||
fairness: false
|
||||
maxWaitMillis: -1
|
||||
minEvictableIdleTimeMillis: 1800000
|
||||
evictorShutdownTimeoutMillis: 10000
|
||||
softMinEvictableIdleTimeMillis: -1
|
||||
numTestsPerEvictionRun: 3
|
||||
timeBetweenEvictionRunsMillis: -1
|
||||
blockWhenExhausted: true
|
||||
jmxEnabled: true
|
||||
testOnBorrow: false
|
||||
testOnReturn: false
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="warn" monitorInterval="60">
|
||||
<Appenders>
|
||||
<Console name="Console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="error" includeLocation="false">
|
||||
<AppenderRef ref="Console" level="error" />
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="warn" monitorInterval="60">
|
||||
<Properties>
|
||||
<Property name="PID">????</Property>
|
||||
<Property name="LOG_PATTERN">%clr{%d{yyyy-MM-dd HH:mm:ss.SSS}}{faint} %clr{%5p} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n%wEx</Property>
|
||||
</Properties>
|
||||
<Appenders>
|
||||
<RollingRandomAccessFile name="Rolling" fileName="/srv/logs/timely/grafana-auth.log"
|
||||
filePattern="/srv/logs/timely/$${date:yyyy-MM}/grafana-auth-%d{MM-dd-yyyy}-%i.log.gz">
|
||||
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||
<Policies>
|
||||
<SizeBasedTriggeringPolicy size="1024 MB" />
|
||||
</Policies>
|
||||
<DefaultRolloverStrategy max="10" />
|
||||
</RollingRandomAccessFile>
|
||||
<Console name="Console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="trace" includeLocation="false">
|
||||
<AppenderRef ref="Console" level="warn" />
|
||||
<AppenderRef ref="Rolling" level="trace" />
|
||||
</Root>
|
||||
<Logger name="timely" additivity="false">
|
||||
<AppenderRef ref="Console" level="debug" />
|
||||
</Logger>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
@@ -0,0 +1,71 @@
|
||||
<beans:beans xmlns="http://www.springframework.org/schema/security"
|
||||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:util="http://www.springframework.org/schema/util"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
|
||||
http://www.springframework.org/schema/security
|
||||
http://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/util
|
||||
http://www.springframework.org/schema/util/spring-util.xsd">
|
||||
|
||||
<beans:bean id="x509PrincipalExtractor" class="org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor">
|
||||
<beans:property name="subjectDnRegex" value="CN=(.*?)(?:,|$)"/>
|
||||
</beans:bean>
|
||||
|
||||
<beans:bean id="timelyUserDetailsService" class="timely.auth.UserDetailsService">
|
||||
<beans:property name="users">
|
||||
<util:list>
|
||||
<beans:bean class="timely.auth.TimelyUser">
|
||||
<beans:constructor-arg name="subjectDn">
|
||||
<beans:value>CN=example.com</beans:value>
|
||||
</beans:constructor-arg>
|
||||
<beans:constructor-arg name="auths">
|
||||
<util:list>
|
||||
<beans:value>D</beans:value>
|
||||
<beans:value>E</beans:value>
|
||||
<beans:value>F</beans:value>
|
||||
</util:list>
|
||||
</beans:constructor-arg>
|
||||
<beans:constructor-arg name="roles">
|
||||
<util:list>
|
||||
<beans:value>G</beans:value>
|
||||
<beans:value>H</beans:value>
|
||||
<beans:value>I</beans:value>
|
||||
</util:list>
|
||||
</beans:constructor-arg>
|
||||
</beans:bean>
|
||||
</util:list>
|
||||
</beans:property>
|
||||
</beans:bean>
|
||||
|
||||
<beans:bean id="x509AuthorizationProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
|
||||
<beans:property name="preAuthenticatedUserDetailsService" ref="timelyUserDetailsService" />
|
||||
</beans:bean>
|
||||
|
||||
<user-service id="usernamePasswordService">
|
||||
<user name="test" password="test1" authorities="A,B,C" />
|
||||
</user-service>
|
||||
|
||||
<beans:bean id="noOpPasswordEncoder" class="org.springframework.security.crypto.password.NoOpPasswordEncoder"/>
|
||||
|
||||
<beans:bean id="UserNamePasswordAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
|
||||
<beans:property name="userDetailsService" ref="usernamePasswordService"/>
|
||||
<beans:property name="passwordEncoder" ref="noOpPasswordEncoder"/>
|
||||
</beans:bean>
|
||||
|
||||
<authentication-manager alias="authenticationManager">
|
||||
<!-- X509 PreAuthenticated authorization provider -->
|
||||
<authentication-provider ref="x509AuthorizationProvider"/>
|
||||
<!-- Basic authorization provider, validates provided username and password -->
|
||||
<authentication-provider ref="UserNamePasswordAuthenticationProvider" />
|
||||
</authentication-manager>
|
||||
|
||||
<util:list id="requiredRoles" value-type="java.lang.String">
|
||||
<beans:value></beans:value>
|
||||
</util:list>
|
||||
|
||||
<util:list id="requiredAuths" value-type="java.lang.String">
|
||||
<beans:value></beans:value>
|
||||
</util:list>
|
||||
</beans:beans>
|
||||
@@ -0,0 +1,296 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>1.2.4</version>
|
||||
</parent>
|
||||
<artifactId>grafana-auth</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jdk8</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.flatbuffers</groupId>
|
||||
<artifactId>flatbuffers-java</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-beanutils</groupId>
|
||||
<artifactId>commons-beanutils</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.lukehutch</groupId>
|
||||
<artifactId>fast-classpath-scanner</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.javacsv</groupId>
|
||||
<artifactId>javacsv</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.accumulo</groupId>
|
||||
<artifactId>accumulo-core</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.accumulo</groupId>
|
||||
<artifactId>accumulo-minicluster</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-framework</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-recipes</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-x-discovery</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hadoop</groupId>
|
||||
<artifactId>hadoop-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hadoop</groupId>
|
||||
<artifactId>hadoop-common</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>jdk.tools</groupId>
|
||||
<artifactId>jdk.tools</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hadoop</groupId>
|
||||
<artifactId>hadoop-minicluster</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-websocket</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-log4j2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-config</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely-balancer</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.lmax</groupId>
|
||||
<artifactId>disruptor</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-tcnative-boringssl-static</artifactId>
|
||||
<version>${version.netty-tcnative}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-slf4j-impl</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.javassist</groupId>
|
||||
<artifactId>javassist</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-test-autoconfigure</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>assemble</id>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>src/main/dist/assembly.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-dependencies</id>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<phase>prepare-package</phase>
|
||||
<configuration>
|
||||
<excludeArtifactIds>junit</excludeArtifactIds>
|
||||
<outputDirectory>${project.build.directory}/lib</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>unpack-native</id>
|
||||
<goals>
|
||||
<goal>unpack</goal>
|
||||
</goals>
|
||||
<phase>process-test-classes</phase>
|
||||
<configuration>
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-tcnative-boringssl-static</artifactId>
|
||||
<version>${version.netty-tcnative}</version>
|
||||
<type>jar</type>
|
||||
<overWrite>false</overWrite>
|
||||
<outputDirectory>${project.build.directory}/native</outputDirectory>
|
||||
<includes>**/*.so</includes>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>add-source</id>
|
||||
<goals>
|
||||
<goal>add-source</goal>
|
||||
</goals>
|
||||
<phase>generate-sources</phase>
|
||||
<configuration>
|
||||
<sources>
|
||||
<source>src/main/generated-java</source>
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<extensions>
|
||||
<!-- Use os-maven-plugin to initialize the "os.detected" properties -->
|
||||
<extension>
|
||||
<groupId>kr.motd.maven</groupId>
|
||||
<artifactId>os-maven-plugin</artifactId>
|
||||
</extension>
|
||||
</extensions>
|
||||
</build>
|
||||
</project>
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
|
||||
<id>dist</id>
|
||||
<formats>
|
||||
<format>tar.gz</format>
|
||||
</formats>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>${project.basedir}</directory>
|
||||
<outputDirectory></outputDirectory>
|
||||
<includes>
|
||||
<include>README*</include>
|
||||
<include>LICENSE*</include>
|
||||
<include>NOTICE*</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.build.directory}</directory>
|
||||
<outputDirectory>lib</outputDirectory>
|
||||
<includes>
|
||||
<include>timely*.jar</include>
|
||||
<include>grafana*.jar</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.build.directory}/lib</directory>
|
||||
<outputDirectory>lib</outputDirectory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.basedir}/bin</directory>
|
||||
<outputDirectory>bin</outputDirectory>
|
||||
<includes>
|
||||
<include>**/*</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.basedir}/conf</directory>
|
||||
<outputDirectory>conf</outputDirectory>
|
||||
<includes>
|
||||
<include>*</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.basedir}/src/main/resources/webapp</directory>
|
||||
<outputDirectory>bin/webapp</outputDirectory>
|
||||
<includes>
|
||||
<include>*</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
@@ -0,0 +1,321 @@
|
||||
package timely.grafana.auth;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.ServerSocketChannel;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.codec.http.HttpContentCompressor;
|
||||
import io.netty.handler.codec.http.HttpContentDecompressor;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpRequestDecoder;
|
||||
import io.netty.handler.codec.http.HttpResponseEncoder;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import io.netty.handler.ssl.ClientAuth;
|
||||
import io.netty.handler.ssl.OpenSslServerContext;
|
||||
import io.netty.handler.ssl.OpenSslServerSessionContext;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import io.netty.handler.ssl.util.SelfSignedCertificate;
|
||||
import io.netty.handler.stream.ChunkedWriteHandler;
|
||||
import io.netty.util.internal.SystemPropertyUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import timely.auth.AuthCache;
|
||||
import timely.balancer.configuration.ClientSsl;
|
||||
import timely.balancer.connection.http.HttpClientPool;
|
||||
import timely.client.http.HttpClient;
|
||||
import timely.configuration.ServerSsl;
|
||||
import timely.grafana.auth.configuration.GrafanaAuthConfiguration;
|
||||
import timely.grafana.auth.configuration.SpringBootstrap;
|
||||
import timely.grafana.auth.netty.http.GrafanaRelayHandler;
|
||||
import timely.grafana.auth.netty.http.GrafanaRequestDecoder;
|
||||
import timely.netty.http.HttpStaticFileServerHandler;
|
||||
import timely.netty.http.NonSslRedirectHandler;
|
||||
import timely.netty.http.TimelyExceptionHandler;
|
||||
import timely.netty.http.auth.X509LoginRequestHandler;
|
||||
|
||||
public class GrafanaAuth {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GrafanaAuth.class);
|
||||
private static final int EPOLL_MIN_MAJOR_VERSION = 2;
|
||||
private static final int EPOLL_MIN_MINOR_VERSION = 6;
|
||||
private static final int EPOLL_MIN_PATCH_VERSION = 32;
|
||||
private static final String OS_NAME = "os.name";
|
||||
private static final String OS_VERSION = "os.version";
|
||||
protected static final CountDownLatch LATCH = new CountDownLatch(1);
|
||||
static ConfigurableApplicationContext applicationContext;
|
||||
|
||||
private final GrafanaAuthConfiguration grafanaAuthConfig;
|
||||
private EventLoopGroup httpWorkerGroup = null;
|
||||
private EventLoopGroup httpBossGroup = null;
|
||||
protected Channel httpChannelHandle = null;
|
||||
protected volatile boolean shutdown = false;
|
||||
|
||||
public GrafanaAuth(GrafanaAuthConfiguration grafanaAuthConfig) throws Exception {
|
||||
this.grafanaAuthConfig = grafanaAuthConfig;
|
||||
}
|
||||
|
||||
protected static ConfigurableApplicationContext initializeConfiguration(String[] args) {
|
||||
return new SpringApplicationBuilder(SpringBootstrap.class).web(WebApplicationType.NONE).run(args);
|
||||
}
|
||||
|
||||
private void shutdownHook() {
|
||||
|
||||
final Runnable shutdownRunner = () -> {
|
||||
if (!shutdown) {
|
||||
shutdown();
|
||||
}
|
||||
};
|
||||
final Thread hook = new Thread(shutdownRunner, "shutdown-hook-thread");
|
||||
Runtime.getRuntime().addShutdownHook(hook);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
List<ChannelFuture> channelFutures = new ArrayList<>();
|
||||
|
||||
if (httpChannelHandle != null) {
|
||||
LOG.info("Closing httpChannelHandle");
|
||||
channelFutures.add(httpChannelHandle.close());
|
||||
}
|
||||
|
||||
// wait for the channels to shutdown
|
||||
channelFutures.forEach(f -> {
|
||||
try {
|
||||
f.get();
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Error while shutting down channel: {}", f.channel().config());
|
||||
LOG.error("{}", e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
Integer quietPeriod = grafanaAuthConfig.getShutdownQuietPeriod();
|
||||
List<Future<?>> groupFutures = new ArrayList<>();
|
||||
|
||||
if (httpBossGroup != null) {
|
||||
LOG.info("Shutting down httpBossGroup");
|
||||
groupFutures.add(httpBossGroup.shutdownGracefully(quietPeriod, 10, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
if (httpWorkerGroup != null) {
|
||||
LOG.info("Shutting down httpWorkerGroup");
|
||||
groupFutures.add(httpWorkerGroup.shutdownGracefully(quietPeriod, 10, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
groupFutures.parallelStream().forEach(f -> {
|
||||
try {
|
||||
f.get();
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Error while shutting down group: {}", f.toString());
|
||||
LOG.error("{}", e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
if (applicationContext != null) {
|
||||
LOG.info("Closing applicationContext");
|
||||
applicationContext.close();
|
||||
}
|
||||
|
||||
this.shutdown = true;
|
||||
LOG.info("Server shut down.");
|
||||
}
|
||||
|
||||
private static boolean useEpoll() {
|
||||
|
||||
// Should we just return true if this is Linux and if we get an error
|
||||
// during Epoll
|
||||
// setup handle it there?
|
||||
final String os = SystemPropertyUtil.get(OS_NAME).toLowerCase().trim();
|
||||
final String[] version = SystemPropertyUtil.get(OS_VERSION).toLowerCase().trim().split("\\.");
|
||||
if (os.startsWith("linux") && version.length >= 3) {
|
||||
final int major = Integer.parseInt(version[0]);
|
||||
if (major > EPOLL_MIN_MAJOR_VERSION) {
|
||||
return true;
|
||||
} else if (major == EPOLL_MIN_MAJOR_VERSION) {
|
||||
final int minor = Integer.parseInt(version[1]);
|
||||
if (minor > EPOLL_MIN_MINOR_VERSION) {
|
||||
return true;
|
||||
} else if (minor == EPOLL_MIN_MINOR_VERSION) {
|
||||
final int patch = Integer.parseInt(version[2].substring(0, 2));
|
||||
return patch >= EPOLL_MIN_PATCH_VERSION;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected SslContext createSSLContext(GrafanaAuthConfiguration config) throws Exception {
|
||||
|
||||
ServerSsl sslCfg = config.getSecurity().getServerSsl();
|
||||
Boolean generate = sslCfg.isUseGeneratedKeypair();
|
||||
SslContextBuilder ssl;
|
||||
if (generate) {
|
||||
LOG.warn("Using generated self signed server certificate");
|
||||
Date begin = new Date();
|
||||
Date end = new Date(begin.getTime() + 86400000);
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate("localhost", begin, end);
|
||||
ssl = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey());
|
||||
} else {
|
||||
String cert = sslCfg.getCertificateFile();
|
||||
String key = sslCfg.getKeyFile();
|
||||
String keyPass = sslCfg.getKeyPassword();
|
||||
if (null == cert || null == key) {
|
||||
throw new IllegalArgumentException("Check your SSL properties, something is wrong.");
|
||||
}
|
||||
ssl = SslContextBuilder.forServer(new File(cert), new File(key), keyPass);
|
||||
}
|
||||
|
||||
ssl.ciphers(sslCfg.getUseCiphers());
|
||||
|
||||
// Can't set to REQUIRE because the CORS pre-flight requests will fail.
|
||||
ssl.clientAuth(ClientAuth.OPTIONAL);
|
||||
|
||||
Boolean useOpenSSL = sslCfg.isUseOpenssl();
|
||||
if (useOpenSSL) {
|
||||
ssl.sslProvider(SslProvider.OPENSSL);
|
||||
} else {
|
||||
ssl.sslProvider(SslProvider.JDK);
|
||||
}
|
||||
String trustStore = sslCfg.getTrustStoreFile();
|
||||
if (null != trustStore) {
|
||||
if (!trustStore.isEmpty()) {
|
||||
ssl.trustManager(new File(trustStore));
|
||||
}
|
||||
}
|
||||
return ssl.build();
|
||||
}
|
||||
|
||||
protected SSLContext createSSLClientContext(GrafanaAuthConfiguration config) throws Exception {
|
||||
|
||||
ClientSsl ssl = config.getSecurity().getClientSsl();
|
||||
return HttpClient.getSSLContext(ssl.getTrustStoreFile(), ssl.getTrustStoreType(), ssl.getTrustStorePassword(),
|
||||
ssl.getKeyFile(), ssl.getKeyType(), ssl.getKeyPassword());
|
||||
}
|
||||
|
||||
public void run() throws Exception {
|
||||
|
||||
AuthCache.configure(grafanaAuthConfig.getSecurity());
|
||||
final boolean useEpoll = useEpoll();
|
||||
Class<? extends ServerSocketChannel> channelClass;
|
||||
if (useEpoll) {
|
||||
httpWorkerGroup = new EpollEventLoopGroup();
|
||||
httpBossGroup = new EpollEventLoopGroup();
|
||||
channelClass = EpollServerSocketChannel.class;
|
||||
} else {
|
||||
httpWorkerGroup = new NioEventLoopGroup();
|
||||
httpBossGroup = new NioEventLoopGroup();
|
||||
channelClass = NioServerSocketChannel.class;
|
||||
}
|
||||
LOG.info("Using channel class {}", channelClass.getSimpleName());
|
||||
|
||||
final int httpPort = grafanaAuthConfig.getHttp().getPort();
|
||||
final String httpIp = grafanaAuthConfig.getHttp().getIp();
|
||||
SslContext sslCtx = createSSLContext(grafanaAuthConfig);
|
||||
if (sslCtx instanceof OpenSslServerContext) {
|
||||
OpenSslServerContext openssl = (OpenSslServerContext) sslCtx;
|
||||
String application = "GrafanaAuth_" + httpPort;
|
||||
OpenSslServerSessionContext opensslCtx = openssl.sessionContext();
|
||||
opensslCtx.setSessionCacheEnabled(true);
|
||||
opensslCtx.setSessionCacheSize(128);
|
||||
opensslCtx.setSessionIdContext(application.getBytes(StandardCharsets.UTF_8));
|
||||
opensslCtx.setSessionTimeout(grafanaAuthConfig.getSecurity().getSessionMaxAge());
|
||||
}
|
||||
SSLContext clientSSLContext = createSSLClientContext(grafanaAuthConfig);
|
||||
final ServerBootstrap httpServer = new ServerBootstrap();
|
||||
httpServer.group(httpBossGroup, httpWorkerGroup);
|
||||
httpServer.channel(channelClass);
|
||||
httpServer.handler(new LoggingHandler());
|
||||
HttpClientPool httpClientPool = new HttpClientPool(grafanaAuthConfig.getSecurity(), grafanaAuthConfig.getHttp(),
|
||||
clientSSLContext);
|
||||
httpServer.childHandler(setupHttpChannel(grafanaAuthConfig, sslCtx, httpClientPool));
|
||||
httpServer.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
|
||||
httpServer.option(ChannelOption.SO_BACKLOG, 128);
|
||||
httpServer.option(ChannelOption.SO_KEEPALIVE, true);
|
||||
httpChannelHandle = httpServer.bind(httpIp, httpPort).sync().channel();
|
||||
final String httpAddress = ((InetSocketAddress) httpChannelHandle.localAddress()).getAddress().getHostAddress();
|
||||
|
||||
shutdownHook();
|
||||
LOG.info("Server started. Listening on {}:{} for HTTP traffic", httpAddress, httpPort);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
GrafanaAuth.applicationContext = GrafanaAuth.initializeConfiguration(args);
|
||||
GrafanaAuthConfiguration balancerConf = applicationContext.getBean(GrafanaAuthConfiguration.class);
|
||||
|
||||
GrafanaAuth b = new GrafanaAuth(balancerConf);
|
||||
try {
|
||||
b.run();
|
||||
LATCH.await();
|
||||
} catch (final InterruptedException e) {
|
||||
LOG.info("Server shutting down.");
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error running server.", e);
|
||||
} finally {
|
||||
try {
|
||||
b.shutdown();
|
||||
} catch (Exception e) {
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected ChannelHandler setupHttpChannel(GrafanaAuthConfiguration config, SslContext sslCtx,
|
||||
HttpClientPool httpClientPool) {
|
||||
|
||||
return new ChannelInitializer<SocketChannel>() {
|
||||
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) throws Exception {
|
||||
|
||||
ch.pipeline().addLast("ssl", new NonSslRedirectHandler(config.getHttp(), sslCtx));
|
||||
ch.pipeline().addLast("encoder", new HttpResponseEncoder());
|
||||
ch.pipeline().addLast("decoder", new HttpRequestDecoder());
|
||||
ch.pipeline().addLast("compressor", new HttpContentCompressor());
|
||||
ch.pipeline().addLast("decompressor", new HttpContentDecompressor());
|
||||
// high maximum contentLength so that grafana snapshots can be delivered
|
||||
// might not be necessary if inbound chunking (while proxying) is handled
|
||||
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(2097152));
|
||||
ch.pipeline().addLast("chunker", new ChunkedWriteHandler());
|
||||
ch.pipeline().addLast("grafanaDecoder",
|
||||
new GrafanaRequestDecoder(config.getSecurity(), config.getHttp()));
|
||||
ch.pipeline().addLast("fileServer", new HttpStaticFileServerHandler());
|
||||
ch.pipeline().addLast("login", new X509LoginRequestHandler(config.getSecurity(), config.getHttp()));
|
||||
ch.pipeline().addLast("httpRelay", new GrafanaRelayHandler(config, httpClientPool));
|
||||
ch.pipeline().addLast("error", new TimelyExceptionHandler());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package timely.grafana.auth.configuration;
|
||||
|
||||
public class Grafana {
|
||||
|
||||
private String host = "localhost";
|
||||
private Integer port = 3000;
|
||||
private String protocol = "http";
|
||||
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public void setPort(Integer port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public Integer getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setProtocol(String protocol) {
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package timely.grafana.auth.configuration;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import timely.balancer.configuration.BalancerHttp;
|
||||
import timely.balancer.configuration.BalancerSecurity;
|
||||
|
||||
@Validated
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "grafana-auth")
|
||||
public class GrafanaAuthConfiguration {
|
||||
|
||||
@Valid
|
||||
@NestedConfigurationProperty
|
||||
private BalancerHttp http = new BalancerHttp();
|
||||
|
||||
@Valid
|
||||
@NestedConfigurationProperty
|
||||
private BalancerSecurity security = new BalancerSecurity();
|
||||
|
||||
@Valid
|
||||
@NestedConfigurationProperty
|
||||
private Grafana grafana = new Grafana();
|
||||
|
||||
private Integer shutdownQuietPeriod = 5;
|
||||
|
||||
public BalancerSecurity getSecurity() {
|
||||
return security;
|
||||
}
|
||||
|
||||
public BalancerHttp getHttp() {
|
||||
return http;
|
||||
}
|
||||
|
||||
public Grafana getGrafana() {
|
||||
return grafana;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time to wait (in seconds) for connections to finish and to make sure no new
|
||||
* connections happen before shutting down Netty event loop groups.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getShutdownQuietPeriod() {
|
||||
return this.shutdownQuietPeriod;
|
||||
}
|
||||
|
||||
public void setShutdownQuietPeriod(Integer quietPeriod) {
|
||||
this.shutdownQuietPeriod = quietPeriod;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package timely.grafana.auth.configuration;
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication(scanBasePackageClasses = { SpringBootstrap.class })
|
||||
public class SpringBootstrap {
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package timely.grafana.auth.netty.http;
|
||||
|
||||
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpDelete;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpHead;
|
||||
import org.apache.http.client.methods.HttpOptions;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import timely.api.request.timeseries.HttpRequest;
|
||||
import timely.api.response.TimelyException;
|
||||
import timely.auth.TimelyPrincipal;
|
||||
import timely.auth.TimelyUser;
|
||||
import timely.auth.util.DnUtils;
|
||||
import timely.auth.util.HttpHeaderUtils;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
import timely.balancer.connection.http.HttpClientPool;
|
||||
import timely.grafana.auth.configuration.GrafanaAuthConfiguration;
|
||||
import timely.grafana.request.GrafanaRequest;
|
||||
import timely.netty.http.TimelyHttpHandler;
|
||||
import timely.netty.http.auth.TimelyAuthenticationToken;
|
||||
|
||||
public class GrafanaRelayHandler extends SimpleChannelInboundHandler<HttpRequest> implements TimelyHttpHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GrafanaRelayHandler.class);
|
||||
private final HttpClientPool httpClientPool;
|
||||
private GrafanaAuthConfiguration config;
|
||||
|
||||
public GrafanaRelayHandler(GrafanaAuthConfiguration config, HttpClientPool httpClientPool) {
|
||||
this.config = config;
|
||||
this.httpClientPool = httpClientPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead0(ChannelHandlerContext ctx, HttpRequest msg) throws Exception {
|
||||
|
||||
CloseableHttpClient client = null;
|
||||
CloseableHttpResponse relayedResponse = null;
|
||||
FullHttpRequest request = null;
|
||||
GrafanaRequest grafanaRequest;
|
||||
TimelyBalancedHost k = null;
|
||||
String relayURI = null;
|
||||
try {
|
||||
try {
|
||||
request = msg.getHttpRequest();
|
||||
String originalURI = request.getUri();
|
||||
originalURI = encodeURI(originalURI);
|
||||
|
||||
if (msg instanceof GrafanaRequest) {
|
||||
grafanaRequest = (GrafanaRequest) msg;
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Request not supported");
|
||||
}
|
||||
TimelyAuthenticationToken token = grafanaRequest.getToken();
|
||||
if (token == null) {
|
||||
throw new IllegalStateException("No token on grafana request");
|
||||
}
|
||||
|
||||
String protocol = config.getGrafana().getProtocol();
|
||||
String host = config.getGrafana().getHost();
|
||||
Integer port = config.getGrafana().getPort();
|
||||
k = TimelyBalancedHost.of(host, -1, port, -1, -1);
|
||||
client = httpClientPool.borrowObject(k);
|
||||
Multimap<String, String> headers = HttpHeaderUtils.toMultimap(request.headers());
|
||||
|
||||
// If incoming user sets these headers, do not relay them
|
||||
headers.removeAll("X-WEBAUTH-USER");
|
||||
headers.removeAll("X-WEBAUTH-NAME");
|
||||
TimelyPrincipal principal = token.getTimelyPrincipal();
|
||||
TimelyUser user = principal.getPrimaryUser();
|
||||
String subjectDn = user.getDn().subjectDN();
|
||||
String userName = DnUtils.getCommonName(subjectDn);
|
||||
String loginName = DnUtils.getShortName(subjectDn);
|
||||
headers.put("X-WEBAUTH-USER", loginName);
|
||||
headers.put("X-WEBAUTH-NAME", userName);
|
||||
List<Header> relayedHeaderList = new ArrayList<>();
|
||||
for (Map.Entry<String, String> h : headers.entries()) {
|
||||
if (!h.getKey().equals(CONTENT_LENGTH)) {
|
||||
relayedHeaderList.add(new BasicHeader(h.getKey(), h.getValue()));
|
||||
}
|
||||
}
|
||||
Header[] headerArray = new Header[relayedHeaderList.size()];
|
||||
relayedHeaderList.toArray(headerArray);
|
||||
relayURI = protocol + "://" + host + ":" + port + originalURI;
|
||||
if (request.getMethod().equals(HttpMethod.HEAD)) {
|
||||
HttpHead relayedRequest = new HttpHead(relayURI);
|
||||
if (headerArray.length > 0) {
|
||||
relayedRequest.setHeaders(headerArray);
|
||||
}
|
||||
relayedResponse = client.execute(relayedRequest);
|
||||
} else if (request.getMethod().equals(HttpMethod.GET)) {
|
||||
HttpGet relayedRequest = new HttpGet(relayURI);
|
||||
if (headerArray.length > 0) {
|
||||
relayedRequest.setHeaders(headerArray);
|
||||
}
|
||||
relayedResponse = client.execute(relayedRequest);
|
||||
} else if (request.getMethod().equals(HttpMethod.POST)) {
|
||||
HttpPost relayedRequest = new HttpPost(relayURI);
|
||||
String content = request.content().toString(StandardCharsets.UTF_8);
|
||||
StringEntity entity = new StringEntity(content);
|
||||
relayedRequest.setEntity(entity);
|
||||
if (headerArray.length > 0) {
|
||||
relayedRequest.setHeaders(headerArray);
|
||||
}
|
||||
relayedResponse = client.execute(relayedRequest);
|
||||
} else if (request.getMethod().equals(HttpMethod.PUT)) {
|
||||
HttpPut relayedRequest = new HttpPut(relayURI);
|
||||
String content = request.content().toString(StandardCharsets.UTF_8);
|
||||
StringEntity entity = new StringEntity(content);
|
||||
relayedRequest.setEntity(entity);
|
||||
if (headerArray.length > 0) {
|
||||
relayedRequest.setHeaders(headerArray);
|
||||
}
|
||||
relayedResponse = client.execute(relayedRequest);
|
||||
} else if (request.getMethod().equals(HttpMethod.DELETE)) {
|
||||
HttpDelete relayedRequest = new HttpDelete(relayURI);
|
||||
if (headerArray.length > 0) {
|
||||
relayedRequest.setHeaders(headerArray);
|
||||
}
|
||||
relayedResponse = client.execute(relayedRequest);
|
||||
} else if (request.getMethod().equals(HttpMethod.OPTIONS)) {
|
||||
HttpOptions relayedRequest = new HttpOptions(relayURI);
|
||||
if (headerArray.length > 0) {
|
||||
relayedRequest.setHeaders(headerArray);
|
||||
}
|
||||
relayedResponse = client.execute(relayedRequest);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String message = e.getMessage();
|
||||
if (message == null) {
|
||||
LOG.error("", e);
|
||||
} else {
|
||||
LOG.error(message, e);
|
||||
}
|
||||
this.sendHttpError(ctx, new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(),
|
||||
e.getMessage(), e.getLocalizedMessage(), e));
|
||||
return;
|
||||
} finally {
|
||||
if (request != null && request instanceof AutoCloseable) {
|
||||
ReferenceCountUtil.release(request);
|
||||
}
|
||||
}
|
||||
|
||||
FullHttpResponse response = null;
|
||||
if (relayedResponse == null) {
|
||||
this.sendHttpError(ctx, new TimelyException(HttpResponseStatus.METHOD_NOT_ALLOWED.code(),
|
||||
"Method not allowed", "Method not allowed"));
|
||||
} else {
|
||||
LOG.debug("RequestURL:{} ResponseCode:{}", relayURI, relayedResponse.getStatusLine().getStatusCode());
|
||||
HttpEntity httpEntity = relayedResponse.getEntity();
|
||||
if (httpEntity == null) {
|
||||
response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
|
||||
HttpResponseStatus.valueOf(relayedResponse.getStatusLine().getStatusCode()));
|
||||
} else {
|
||||
ByteArrayOutputStream baos = new MyByteArrayOutputStream();
|
||||
StreamUtils.copy(httpEntity.getContent(), baos);
|
||||
response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
|
||||
HttpResponseStatus.valueOf(relayedResponse.getStatusLine().getStatusCode()),
|
||||
Unpooled.copiedBuffer(baos.toByteArray()));
|
||||
response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
|
||||
}
|
||||
for (Header h : relayedResponse.getAllHeaders()) {
|
||||
response.headers().add(h.getName(), h.getValue());
|
||||
}
|
||||
sendResponse(ctx, response);
|
||||
}
|
||||
} finally {
|
||||
if (relayedResponse != null) {
|
||||
relayedResponse.close();
|
||||
}
|
||||
if (client != null && k != null) {
|
||||
httpClientPool.returnObject(k, client);
|
||||
} else {
|
||||
LOG.error("NOT RETURNING CONNECTION! " + msg.getHttpRequest().getUri());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static public class MyByteArrayOutputStream extends ByteArrayOutputStream {
|
||||
|
||||
public MyByteArrayOutputStream() {
|
||||
}
|
||||
|
||||
public MyByteArrayOutputStream(int size) {
|
||||
super(size);
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public byte[] getBuf() {
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
static public String encodeURI(String s) {
|
||||
try {
|
||||
s = s.replaceAll("\\{", URLEncoder.encode("{", "UTF-8"));
|
||||
s = s.replaceAll("\\}", URLEncoder.encode("}", "UTF-8"));
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package timely.grafana.auth.netty.http;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpHeaders.Names;
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.api.request.AuthenticatedRequest;
|
||||
import timely.api.request.timeseries.HttpRequest;
|
||||
import timely.api.response.StrictTransportResponse;
|
||||
import timely.api.response.TimelyException;
|
||||
import timely.auth.AuthCache;
|
||||
import timely.auth.AuthenticationService;
|
||||
import timely.auth.SubjectIssuerDNPair;
|
||||
import timely.auth.TimelyPrincipal;
|
||||
import timely.auth.util.HttpHeaderUtils;
|
||||
import timely.configuration.Http;
|
||||
import timely.configuration.Security;
|
||||
import timely.grafana.request.GrafanaRequest;
|
||||
import timely.netty.Constants;
|
||||
import timely.netty.http.TimelyHttpHandler;
|
||||
import timely.netty.http.auth.TimelyAuthenticationToken;
|
||||
|
||||
public class GrafanaRequestDecoder extends MessageToMessageDecoder<FullHttpRequest> implements TimelyHttpHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GrafanaRequestDecoder.class);
|
||||
private static final String LOG_RECEIVED_REQUEST = "Received HTTP request {}";
|
||||
private static final String LOG_PARSED_REQUEST = "Parsed request {}";
|
||||
|
||||
private final Security security;
|
||||
private final String nonSecureRedirectAddress;
|
||||
|
||||
public GrafanaRequestDecoder(Security security, Http http) {
|
||||
|
||||
this.security = security;
|
||||
this.nonSecureRedirectAddress = http.getRedirectPath();
|
||||
}
|
||||
|
||||
public static String getSessionId(FullHttpRequest msg) {
|
||||
Multimap<String, String> headers = HttpHeaderUtils.toMultimap(msg.headers());
|
||||
Collection<String> cookies = headers.get(Names.COOKIE);
|
||||
final StringBuilder buf = new StringBuilder();
|
||||
cookies.forEach(h -> {
|
||||
ServerCookieDecoder.STRICT.decode(h).forEach(c -> {
|
||||
if (c.name().equals(Constants.COOKIE_NAME)) {
|
||||
if (buf.length() == 0) {
|
||||
buf.append(c.value());
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
if (buf.length() == 0) {
|
||||
return null;
|
||||
} else {
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, FullHttpRequest msg, List<Object> out) throws Exception {
|
||||
|
||||
LOG.trace(LOG_RECEIVED_REQUEST, msg);
|
||||
|
||||
final String uri = msg.getUri();
|
||||
final QueryStringDecoder decoder = new QueryStringDecoder(uri);
|
||||
if (decoder.path().equals(nonSecureRedirectAddress)) {
|
||||
out.add(new StrictTransportResponse());
|
||||
return;
|
||||
}
|
||||
|
||||
final String sessionId = getSessionId(msg);
|
||||
LOG.trace("SessionID: " + sessionId);
|
||||
|
||||
HttpRequest request;
|
||||
try {
|
||||
request = new GrafanaRequest();
|
||||
Multimap<String, String> headers = HttpHeaderUtils.toMultimap(msg.headers());
|
||||
((AuthenticatedRequest) request).addHeaders(headers);
|
||||
X509Certificate clientCert = AuthenticationService.getClientCertificate(ctx);
|
||||
TimelyAuthenticationToken token;
|
||||
if (StringUtils.isNotBlank(sessionId)) {
|
||||
TimelyPrincipal principal;
|
||||
((AuthenticatedRequest) request).setSessionId(sessionId);
|
||||
if (AuthCache.containsKey(sessionId)) {
|
||||
principal = AuthCache.get(sessionId);
|
||||
} else {
|
||||
principal = TimelyPrincipal.anonymousPrincipal();
|
||||
}
|
||||
SubjectIssuerDNPair dn = principal.getPrimaryUser().getDn();
|
||||
token = AuthenticationService.getAuthenticationToken(null, dn.subjectDN(), dn.issuerDN());
|
||||
} else if (clientCert != null) {
|
||||
token = AuthenticationService.getAuthenticationToken(clientCert, headers);
|
||||
} else {
|
||||
SubjectIssuerDNPair dn = TimelyPrincipal.anonymousPrincipal().getPrimaryUser().getDn();
|
||||
token = AuthenticationService.getAuthenticationToken(null, dn.subjectDN(), dn.issuerDN());
|
||||
}
|
||||
((AuthenticatedRequest) request).setToken(token);
|
||||
request.setHttpRequest(msg.copy());
|
||||
LOG.trace(LOG_PARSED_REQUEST, request);
|
||||
request.validate();
|
||||
out.add(request);
|
||||
} catch (UnsupportedOperationException | NullPointerException e) {
|
||||
// Return the original http request to route to the static file
|
||||
// server
|
||||
msg.retain();
|
||||
out.add(msg);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
try {
|
||||
AuthenticationService.enforceAccess((AuthenticatedRequest) request);
|
||||
} catch (TimelyException e) {
|
||||
if (!security.isAllowAnonymousHttpAccess()) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
out.clear();
|
||||
throw e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package timely.grafana.request;
|
||||
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
import timely.api.annotation.Http;
|
||||
import timely.api.request.AuthenticatedRequest;
|
||||
import timely.api.request.HttpGetRequest;
|
||||
import timely.api.request.HttpPostRequest;
|
||||
|
||||
@Http(path = "/")
|
||||
public class GrafanaRequest extends AuthenticatedRequest implements HttpGetRequest, HttpPostRequest {
|
||||
|
||||
private FullHttpRequest httpRequest = null;
|
||||
|
||||
@Override
|
||||
public HttpGetRequest parseQueryParameters(QueryStringDecoder decoder) throws Exception {
|
||||
return new GrafanaRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpPostRequest parseBody(String content) throws Exception {
|
||||
return new GrafanaRequest();
|
||||
}
|
||||
|
||||
public void setHttpRequest(FullHttpRequest httpRequest) {
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
|
||||
public FullHttpRequest getHttpRequest() {
|
||||
return httpRequest;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<FindBugsFilter>
|
||||
<Match>
|
||||
<Class name="timely.grafana.auth.netty.http.GrafanaRelayHandler$MyByteArrayOutputStream" />
|
||||
<Method name="getBuf" />
|
||||
<Bug pattern="EI_EXPOSE_REP" />
|
||||
</Match>
|
||||
</FindBugsFilter>
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
<version>1.2.4</version>
|
||||
</parent>
|
||||
<artifactId>timely-app</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
Vendored
+2
-2
@@ -23,8 +23,8 @@
|
||||
{"name": "Project site", "url": "https://github.com/NationalSecurityAgency/timely"},
|
||||
{"name": "License & Terms", "url": "https://raw.githubusercontent.com/NationalSecurityAgency/timely/master/LICENSE"}
|
||||
],
|
||||
"version": "0.0.6-SNAPSHOT",
|
||||
"updated": "2017-08-11"
|
||||
"version": "1.2.4",
|
||||
"updated": "2019-05-22"
|
||||
},
|
||||
|
||||
"includes": [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "timely-app",
|
||||
"private": true,
|
||||
"version": "0.0.2",
|
||||
"version": "1.2.4",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
{"name": "Project site", "url": "https://github.com/NationalSecurityAgency/timely"},
|
||||
{"name": "License & Terms", "url": "https://raw.githubusercontent.com/NationalSecurityAgency/timely/master/LICENSE"}
|
||||
],
|
||||
"version": "0.0.6-SNAPSHOT",
|
||||
"updated": "2017-08-11"
|
||||
"version": "1.2.4",
|
||||
"updated": "2019-05-22"
|
||||
},
|
||||
|
||||
"includes": [
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
<version>1.2.4</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Timely</name>
|
||||
<description>A secure and scalable time series database based on Apache Accumulo</description>
|
||||
@@ -20,6 +20,7 @@
|
||||
<module>grafana</module>
|
||||
<module>analytics</module>
|
||||
<module>balancer</module>
|
||||
<module>grafana-auth</module>
|
||||
</modules>
|
||||
<scm>
|
||||
<connection>scm:git:https://github.com/NationalSecurityAgency/timely.git</connection>
|
||||
@@ -40,33 +41,31 @@
|
||||
<os.detection.classifierWithLikes>fedora</os.detection.classifierWithLikes>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<version.accumulo>1.7.4</version.accumulo>
|
||||
<version.accumulo>1.9.2</version.accumulo>
|
||||
<version.buildnumber-maven>1.4</version.buildnumber-maven>
|
||||
<version.caffeine>2.3.3</version.caffeine>
|
||||
<version.collectd>5.5.1</version.collectd>
|
||||
<version.commons-collections4>4.1</version.commons-collections4>
|
||||
<version.commons-configuration>1.10</version.commons-configuration>
|
||||
<version.commons-io>2.6</version.commons-io>
|
||||
<version.commons-lang3>3.5</version.commons-lang3>
|
||||
<version.commons-pool2>2.6.0</version.commons-pool2>
|
||||
<version.curator>2.11.1</version.curator>
|
||||
<version.disruptor>3.3.6</version.disruptor>
|
||||
<version.caffeine>2.6.2</version.caffeine>
|
||||
<version.collectd>5.7.1</version.collectd>
|
||||
<version.commons-collections4>4.3</version.commons-collections4>
|
||||
<version.commons-lang3>3.8.1</version.commons-lang3>
|
||||
<version.commons-pool2>2.6.1</version.commons-pool2>
|
||||
<version.curator>4.1.0</version.curator>
|
||||
<version.disruptor>3.4.2</version.disruptor>
|
||||
<version.eclipse-lifecycle>1.0.0</version.eclipse-lifecycle>
|
||||
<version.fast-classpath-scanner>2.0.5</version.fast-classpath-scanner>
|
||||
<version.fast-classpath-scanner>3.1.13</version.fast-classpath-scanner>
|
||||
<version.flink>1.1.4</version.flink>
|
||||
<version.google-flatbuffers>1.10.0</version.google-flatbuffers>
|
||||
<version.guava>19.0</version.guava>
|
||||
<version.guava>27.1-jre</version.guava>
|
||||
<version.hadoop>2.7.3</version.hadoop>
|
||||
<version.httpcomponents>4.5.2</version.httpcomponents>
|
||||
<version.httpcomponents>4.5.6</version.httpcomponents>
|
||||
<version.impsort>1.2.0</version.impsort>
|
||||
<version.jackson>2.9.7</version.jackson>
|
||||
<version.jackson>2.9.8</version.jackson>
|
||||
<version.jacoco>0.7.6.201602180812</version.jacoco>
|
||||
<version.javacsv>2.0</version.javacsv>
|
||||
<version.javassist>3.21.0-GA</version.javassist>
|
||||
<version.javassist>3.24.1-GA</version.javassist>
|
||||
<version.jcip-annotations>1.0</version.jcip-annotations>
|
||||
<version.jsoup>1.9.2</version.jsoup>
|
||||
<version.jsoup>1.11.3</version.jsoup>
|
||||
<version.junit>4.12</version.junit>
|
||||
<version.log4j>2.6.2</version.log4j>
|
||||
<version.log4j>2.11.2</version.log4j>
|
||||
<version.maven-assembly>2.6</version.maven-assembly>
|
||||
<version.maven-build-helper>1.12</version.maven-build-helper>
|
||||
<version.maven-compiler>3.3</version.maven-compiler>
|
||||
@@ -76,14 +75,14 @@
|
||||
<version.maven-rpm>2.1.4</version.maven-rpm>
|
||||
<version.maven-shade>2.4.2</version.maven-shade>
|
||||
<version.maven-surefire>2.19.1</version.maven-surefire>
|
||||
<version.netty>4.0.56.Final</version.netty>
|
||||
<version.netty-tcnative>2.0.7.Final</version.netty-tcnative>
|
||||
<version.netty>4.1.35.Final</version.netty>
|
||||
<version.netty-tcnative>2.0.25.Final</version.netty-tcnative>
|
||||
<version.os-maven>1.5.0.Final</version.os-maven>
|
||||
<version.powermock>1.6.4</version.powermock>
|
||||
<version.slf4j>1.7.21</version.slf4j>
|
||||
<version.slf4j>1.7.26</version.slf4j>
|
||||
<version.spotbugs-maven>3.1.7</version.spotbugs-maven>
|
||||
<version.spring-boot>1.4.1.RELEASE</version.spring-boot>
|
||||
<version.tyrus>1.13</version.tyrus>
|
||||
<version.spring-boot>2.1.3.RELEASE</version.spring-boot>
|
||||
<version.tyrus>1.15</version.tyrus>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
@@ -102,6 +101,11 @@
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${version.jackson}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-guava</artifactId>
|
||||
<version>${version.jackson}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jdk8</artifactId>
|
||||
@@ -135,7 +139,7 @@
|
||||
<dependency>
|
||||
<groupId>io.github.lukehutch</groupId>
|
||||
<artifactId>fast-classpath-scanner</artifactId>
|
||||
<version>2.0.5</version>
|
||||
<version>${version.fast-classpath-scanner}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
@@ -151,6 +155,12 @@
|
||||
<groupId>org.apache.accumulo</groupId>
|
||||
<artifactId>accumulo-core</artifactId>
|
||||
<version>${version.accumulo}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.accumulo</groupId>
|
||||
@@ -304,6 +314,11 @@
|
||||
<artifactId>collectd-plugin-parent</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely-balancer</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely-client</artifactId>
|
||||
@@ -441,7 +456,7 @@
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>${version.maven-surefire}</version>
|
||||
<configuration>
|
||||
<argLine>${failsafeArgLine} -Djava.library.path=${project.build.directory}/native/META-INF/native/libnetty-tcnative.so -Dzookeeper.sasl.client=false</argLine>
|
||||
<argLine>${failsafeArgLine} -Djava.library.path=${project.build.directory}/native/META-INF/native/libnetty_tcnative_linux_x86_64.so -Dzookeeper.sasl.client=false</argLine>
|
||||
<includes>
|
||||
<include>**/*.java</include>
|
||||
</includes>
|
||||
@@ -484,7 +499,7 @@
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>${version.maven-surefire}</version>
|
||||
<configuration>
|
||||
<argLine>${sureFireArgLine} -Djava.library.path=${project.build.directory}/native/META-INF/native/libnetty-tcnative.so -Dzookeeper.sasl.client=false</argLine>
|
||||
<argLine>${sureFireArgLine} -Djava.library.path=${project.build.directory}/native/META-INF/native/libnetty-tcnative_linux_x86_64.so -Dzookeeper.sasl.client=false</argLine>
|
||||
<excludedGroups>timely.test.IntegrationTest</excludedGroups>
|
||||
<redirectTestOutputToFile>true</redirectTestOutputToFile>
|
||||
</configuration>
|
||||
|
||||
@@ -8,11 +8,6 @@ else
|
||||
TCNATIVE_SUFFIX="so"
|
||||
fi
|
||||
|
||||
# netty tcnative file reference
|
||||
#netty-tcnative-1.1.33.Fork18-linux-x86_64-fedora.jar -> libnetty-tcnative.so
|
||||
#netty-tcnative-1.1.33.Fork18-linux-x86_64.jar -> libnetty-tcnative.so
|
||||
#netty-tcnative-1.1.33.Fork18-osx-x86_64.jar -> libnetty-tcnative.jnilib
|
||||
|
||||
THIS_DIR="${THIS_SCRIPT%/*}"
|
||||
NATIVE_DIR="${THIS_DIR}/META-INF/native"
|
||||
BASE_DIR=${THIS_DIR}/..
|
||||
@@ -32,12 +27,12 @@ fi
|
||||
mkdir -p ${NATIVE_DIR}
|
||||
|
||||
pushd ${BASE_DIR}/bin
|
||||
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative*.jar META-INF/native/libnetty_tcnative.${NATIVE_SUFFIX}
|
||||
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-transport-native-epoll*.jar META-INF/native/libnetty_transport_native_epoll_x86_64.${NATIVE_SUFFIX}
|
||||
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative-boringssl-static*.jar META-INF/native/libnetty_tcnative_linux_x86_64.${TCNATIVE_SUFFIX}
|
||||
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-all*.jar META-INF/native/libnetty_transport_native_epoll_x86_64.${TCNATIVE_SUFFIX}
|
||||
popd
|
||||
|
||||
export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*"
|
||||
JVM_ARGS="-Xmx256m -Xms256m -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS}"
|
||||
export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*:${HADOOP_CONF_DIR}"
|
||||
JVM_ARGS="-Xmx256m -Xms256m -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS} -Dlog4j.configurationFile=${CONF_DIR}/log4j2-spring.xml"
|
||||
JVM_ARGS="${JVM_ARGS} -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"
|
||||
JVM_ARGS="${JVM_ARGS} -Djava.library.path=${NATIVE_DIR}"
|
||||
|
||||
|
||||
@@ -2,17 +2,12 @@
|
||||
|
||||
if [[ `uname` == "Darwin" ]]; then
|
||||
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
|
||||
TCNATIVE_SUFFIX="tcnative.jnilib"
|
||||
TCNATIVE_SUFFIX="jnilib"
|
||||
else
|
||||
THIS_SCRIPT=`readlink -f $0`
|
||||
TCNATIVE_SUFFIX="tcnative.so"
|
||||
TCNATIVE_SUFFIX="so"
|
||||
fi
|
||||
|
||||
# netty tcnative file reference
|
||||
#netty-tcnative-1.1.33.Fork18-linux-x86_64-fedora.jar -> libnetty-tcnative.so
|
||||
#netty-tcnative-1.1.33.Fork18-linux-x86_64.jar -> libnetty-tcnative.so
|
||||
#netty-tcnative-1.1.33.Fork18-osx-x86_64.jar -> libnetty-tcnative.jnilib
|
||||
|
||||
THIS_DIR="${THIS_SCRIPT%/*}"
|
||||
NATIVE_DIR="${THIS_DIR}/META-INF/native"
|
||||
BASE_DIR=${THIS_DIR}/..
|
||||
@@ -32,11 +27,12 @@ fi
|
||||
mkdir -p ${NATIVE_DIR}
|
||||
|
||||
pushd ${BASE_DIR}/bin
|
||||
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative*.jar META-INF/native/libnetty-${TCNATIVE_SUFFIX}
|
||||
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative-boringssl-static*.jar META-INF/native/libnetty_tcnative_linux_x86_64.${TCNATIVE_SUFFIX}
|
||||
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-all*.jar META-INF/native/libnetty_transport_native_epoll_x86_64.${TCNATIVE_SUFFIX}
|
||||
popd
|
||||
|
||||
export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*"
|
||||
JVM_ARGS="-Xmx256m -Xms256m -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS}"
|
||||
export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*:${HADOOP_CONF_DIR}"
|
||||
JVM_ARGS="-Xmx256m -Xms256m -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS} -Dlog4j.configurationFile=${CONF_DIR}/log4j2-spring.xml"
|
||||
JVM_ARGS="${JVM_ARGS} -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"
|
||||
JVM_ARGS="${JVM_ARGS} -Djava.library.path=${NATIVE_DIR}"
|
||||
# for debugging
|
||||
|
||||
@@ -26,12 +26,12 @@
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Logger name="timely.subscription" level="trace" additivity="false">
|
||||
<AppenderRef ref="Subscription" />
|
||||
</Logger>
|
||||
<Root level="trace" includeLocation="false">
|
||||
<Root level="debug" includeLocation="false">
|
||||
<AppenderRef ref="Console" level="warn" />
|
||||
<AppenderRef ref="Rolling" level="trace" />
|
||||
<AppenderRef ref="Rolling" level="debug" />
|
||||
</Root>
|
||||
<Logger name="timely" additivity="false">
|
||||
<AppenderRef ref="Console" level="debug" />
|
||||
</Logger>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
+34
-12
@@ -15,15 +15,27 @@
|
||||
|
||||
<beans:bean id="timelyUserDetailsService" class="timely.auth.UserDetailsService">
|
||||
<beans:property name="users">
|
||||
<util:map key-type="java.lang.String">
|
||||
<beans:entry key="example.com">
|
||||
<util:list>
|
||||
<beans:value>D</beans:value>
|
||||
<beans:value>E</beans:value>
|
||||
<beans:value>F</beans:value>
|
||||
</util:list>
|
||||
</beans:entry>
|
||||
</util:map>
|
||||
<util:list>
|
||||
<beans:bean class="timely.auth.TimelyUser">
|
||||
<beans:constructor-arg name="subjectDn">
|
||||
<beans:value>CN=example.com</beans:value>
|
||||
</beans:constructor-arg>
|
||||
<beans:constructor-arg name="auths">
|
||||
<util:list>
|
||||
<beans:value>D</beans:value>
|
||||
<beans:value>E</beans:value>
|
||||
<beans:value>F</beans:value>
|
||||
</util:list>
|
||||
</beans:constructor-arg>
|
||||
<beans:constructor-arg name="roles">
|
||||
<util:list>
|
||||
<beans:value>G</beans:value>
|
||||
<beans:value>H</beans:value>
|
||||
<beans:value>I</beans:value>
|
||||
</util:list>
|
||||
</beans:constructor-arg>
|
||||
</beans:bean>
|
||||
</util:list>
|
||||
</beans:property>
|
||||
</beans:bean>
|
||||
|
||||
@@ -34,9 +46,12 @@
|
||||
<user-service id="usernamePasswordService">
|
||||
<user name="test" password="test1" authorities="A,B,C" />
|
||||
</user-service>
|
||||
|
||||
<beans:bean id="UserNamePasswordAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
|
||||
|
||||
<beans:bean id="noOpPasswordEncoder" class="org.springframework.security.crypto.password.NoOpPasswordEncoder"/>
|
||||
|
||||
<beans:bean id="UserNamePasswordAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
|
||||
<beans:property name="userDetailsService" ref="usernamePasswordService"/>
|
||||
<beans:property name="passwordEncoder" ref="noOpPasswordEncoder"/>
|
||||
</beans:bean>
|
||||
|
||||
<authentication-manager alias="authenticationManager">
|
||||
@@ -45,5 +60,12 @@
|
||||
<!-- Basic authorization provider, validates provided username and password -->
|
||||
<authentication-provider ref="UserNamePasswordAuthenticationProvider" />
|
||||
</authentication-manager>
|
||||
|
||||
|
||||
<util:list id="requiredRoles" value-type="java.lang.String">
|
||||
<beans:value></beans:value>
|
||||
</util:list>
|
||||
|
||||
<util:list id="requiredAuths" value-type="java.lang.String">
|
||||
<beans:value></beans:value>
|
||||
</util:list>
|
||||
</beans:beans>
|
||||
@@ -18,7 +18,10 @@ timely:
|
||||
max-downsample-memory: 10000000
|
||||
security:
|
||||
session-max-age: 86400
|
||||
allow-anonymous-access: true
|
||||
cache-expiration-minutes: 60
|
||||
cache-refresh-minutes: 5
|
||||
allow-anonymous-http-access: true
|
||||
allow-anonymous-ws-access: true
|
||||
ssl:
|
||||
certificate-file: /timely-server-src/server/pki/timely.csr
|
||||
key-file: /timely-server-src/server/pki/timely-pkcs8.key
|
||||
@@ -49,9 +52,9 @@ timely:
|
||||
timeout: 60
|
||||
subscription-lag: 120
|
||||
meta-cache:
|
||||
expiration-minutes: 60
|
||||
initial-capacity: 2000
|
||||
max-capacity: 10000
|
||||
expiration-minutes: 1440
|
||||
cache-refresh-minutes: 60
|
||||
max-tag-values: 100
|
||||
visibility-cache:
|
||||
expiration-minutes: 60
|
||||
initial-capacity: 2000
|
||||
|
||||
@@ -18,7 +18,10 @@ timely:
|
||||
max-downsample-memory: 10000000
|
||||
security:
|
||||
session-max-age: 86400
|
||||
allow-anonymous-access: true
|
||||
cache-expiration-minutes: 60
|
||||
cache-refresh-minutes: 5
|
||||
allow-anonymous-http-access: true
|
||||
allow-anonymous-ws-access: true
|
||||
ssl:
|
||||
certificate-file:
|
||||
key-file:
|
||||
@@ -53,9 +56,9 @@ timely:
|
||||
scanner-read-ahead: 1
|
||||
subscription-batch-size: 5000
|
||||
meta-cache:
|
||||
expiration-minutes: 60
|
||||
initial-capacity: 2000
|
||||
max-capacity: 10000
|
||||
expiration-minutes: 1440
|
||||
cache-refresh-minutes: 60
|
||||
max-tag-values: 100
|
||||
visibility-cache:
|
||||
expiration-minutes: 60
|
||||
initial-capacity: 2000
|
||||
|
||||
+24
-21
@@ -4,14 +4,15 @@ timely:
|
||||
metric-age-off-days:
|
||||
default: 7
|
||||
cache:
|
||||
enabled:
|
||||
enabled: true
|
||||
age-off-hours:
|
||||
default: 30
|
||||
default: 26
|
||||
metrics-report-ignored-tags:
|
||||
defaultVisibility:
|
||||
accumulo:
|
||||
instance-name:
|
||||
zookeepers:
|
||||
username:
|
||||
instance-name: accumulo
|
||||
zookeepers: localhost:2181
|
||||
username: root
|
||||
password:
|
||||
write:
|
||||
latency: 5s
|
||||
@@ -22,23 +23,26 @@ timely:
|
||||
max-downsample-memory: 10000000
|
||||
security:
|
||||
session-max-age: 86400
|
||||
allow-anonymous-access: false
|
||||
ssl:
|
||||
cache-expiration-minutes: 60
|
||||
cache-refresh-minutes: 5
|
||||
allow-anonymous-http-access: true
|
||||
allow-anonymous-ws-access: true
|
||||
serverSsl:
|
||||
certificate-file:
|
||||
key-file:
|
||||
key-password:
|
||||
use-generated-keypair: false
|
||||
use-generated-keypair: true
|
||||
trust-store-file:
|
||||
use-openssl: true
|
||||
#use-ciphers:
|
||||
server:
|
||||
ip:
|
||||
tcp-port:
|
||||
udp-port:
|
||||
ip: 0.0.0.0
|
||||
tcp-port: 4242
|
||||
udp-port: 4542
|
||||
http:
|
||||
host:
|
||||
ip:
|
||||
port:
|
||||
host: localhost
|
||||
ip: 0.0.0.0
|
||||
port: 4243
|
||||
redirect-path: /secure-me
|
||||
strict-transport-max-age: 608400
|
||||
cors:
|
||||
@@ -49,8 +53,8 @@ timely:
|
||||
allowed-methods: DELETE,GET,HEAD,OPTIONS,PUT,POST
|
||||
allowed-headers: content-type
|
||||
websocket:
|
||||
ip:
|
||||
port:
|
||||
ip: 0.0.0.0
|
||||
port: 4244
|
||||
timeout: 60
|
||||
subscription-lag: 120
|
||||
scanner-batch-size: 10000
|
||||
@@ -58,10 +62,9 @@ timely:
|
||||
scanner-read-ahead: 1
|
||||
subscription-batch-size: 5000
|
||||
meta-cache:
|
||||
expiration-minutes: 60
|
||||
initial-capacity: 2000
|
||||
max-capacity: 10000
|
||||
expiration-minutes: 1440
|
||||
cache-refresh-minutes: 60
|
||||
max-tag-values: 100
|
||||
visibility-cache:
|
||||
expiration-minutes: 60
|
||||
initial-capacity: 2000
|
||||
max-capacity: 10000
|
||||
|
||||
+7
-38
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
<version>1.2.4</version>
|
||||
</parent>
|
||||
<artifactId>timely-server</artifactId>
|
||||
<dependencies>
|
||||
@@ -20,6 +20,10 @@
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-guava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jdk8</artifactId>
|
||||
@@ -145,9 +149,8 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-tcnative</artifactId>
|
||||
<artifactId>netty-tcnative-boringssl-static</artifactId>
|
||||
<version>${version.netty-tcnative}</version>
|
||||
<classifier>${os.detected.classifier}</classifier>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -236,9 +239,8 @@
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-tcnative</artifactId>
|
||||
<artifactId>netty-tcnative-boringssl-static</artifactId>
|
||||
<version>${version.netty-tcnative}</version>
|
||||
<classifier>${os.detected.classifier}</classifier>
|
||||
<type>jar</type>
|
||||
<overWrite>false</overWrite>
|
||||
<outputDirectory>${project.build.directory}/native</outputDirectory>
|
||||
@@ -280,37 +282,4 @@
|
||||
</extension>
|
||||
</extensions>
|
||||
</build>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>native-epoll-linux</id>
|
||||
<activation>
|
||||
<os>
|
||||
<family>!mac</family>
|
||||
</os>
|
||||
</activation>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-transport-native-epoll</artifactId>
|
||||
<version>${version.netty}</version>
|
||||
<classifier>linux-x86_64</classifier>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>native-epoll</id>
|
||||
<activation>
|
||||
<os>
|
||||
<family>mac</family>
|
||||
</os>
|
||||
</activation>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-transport-native-epoll</artifactId>
|
||||
<version>${version.netty}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
Vendored
+1
-1
@@ -33,7 +33,7 @@
|
||||
<directory>${project.basedir}/bin</directory>
|
||||
<outputDirectory>bin</outputDirectory>
|
||||
<includes>
|
||||
<include>*</include>
|
||||
<include>**/*</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package timely;
|
||||
|
||||
import static timely.store.cache.DataStoreCache.NON_CACHED_METRICS;
|
||||
import static timely.store.cache.DataStoreCache.NON_CACHED_METRICS_LOCK_PATH;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
@@ -63,9 +66,11 @@ import org.apache.curator.x.discovery.ServiceDiscovery;
|
||||
import org.apache.curator.x.discovery.ServiceDiscoveryBuilder;
|
||||
import org.apache.curator.x.discovery.ServiceInstance;
|
||||
import org.apache.curator.x.discovery.ServiceInstanceBuilder;
|
||||
import org.apache.zookeeper.CreateMode;
|
||||
import org.apache.zookeeper.data.Stat;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import timely.api.response.TimelyException;
|
||||
@@ -97,7 +102,7 @@ import timely.netty.udp.UdpDecoder;
|
||||
import timely.netty.udp.UdpPacketToByteBuf;
|
||||
import timely.netty.websocket.WSMetricPutHandler;
|
||||
import timely.netty.websocket.WSVersionRequestHandler;
|
||||
import timely.netty.websocket.WebSocketHttpCookieHandler;
|
||||
import timely.netty.websocket.WebSocketFullRequestHandler;
|
||||
import timely.netty.websocket.WebSocketRequestDecoder;
|
||||
import timely.netty.websocket.subscription.WSAddSubscriptionRequestHandler;
|
||||
import timely.netty.websocket.subscription.WSCloseSubscriptionRequestHandler;
|
||||
@@ -125,6 +130,7 @@ public class Server {
|
||||
private static final String OS_VERSION = "os.version";
|
||||
private static final String WS_PATH = "/websocket";
|
||||
|
||||
private CuratorFramework curatorFramework;
|
||||
protected static final CountDownLatch LATCH = new CountDownLatch(1);
|
||||
static ConfigurableApplicationContext applicationContext;
|
||||
|
||||
@@ -146,6 +152,9 @@ public class Server {
|
||||
protected volatile boolean shutdown = false;
|
||||
private final int DEFAULT_EVENT_LOOP_THREADS;
|
||||
|
||||
private String[] zkPaths = new String[] { SERVICE_DISCOVERY_PATH, NON_CACHED_METRICS,
|
||||
NON_CACHED_METRICS_LOCK_PATH };
|
||||
|
||||
private static boolean useEpoll() {
|
||||
|
||||
// Should we just return true if this is Linux and if we get an error
|
||||
@@ -175,13 +184,8 @@ public class Server {
|
||||
}
|
||||
}
|
||||
|
||||
public void registerService() {
|
||||
public void registerService(CuratorFramework curatorFramework) {
|
||||
try {
|
||||
RetryPolicy retryPolicy = new RetryForever(1000);
|
||||
CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(config.getAccumulo().getZookeepers(),
|
||||
30000, 10000, retryPolicy);
|
||||
curatorFramework.start();
|
||||
|
||||
try {
|
||||
Stat stat = curatorFramework.checkExists().forPath(SERVICE_DISCOVERY_PATH);
|
||||
if (stat == null) {
|
||||
@@ -247,7 +251,7 @@ public class Server {
|
||||
}
|
||||
|
||||
protected static Configuration initializeConfiguration(String[] args) {
|
||||
applicationContext = new SpringApplicationBuilder(SpringBootstrap.class).web(false).run(args);
|
||||
applicationContext = new SpringApplicationBuilder(SpringBootstrap.class).web(WebApplicationType.NONE).run(args);
|
||||
return applicationContext.getBean(Configuration.class);
|
||||
}
|
||||
|
||||
@@ -369,6 +373,10 @@ public class Server {
|
||||
LOG.error("Error closing WebSocketRequestDecoder during shutdown", e);
|
||||
}
|
||||
|
||||
if (curatorFramework != null) {
|
||||
curatorFramework.close();
|
||||
}
|
||||
|
||||
if (applicationContext != null) {
|
||||
LOG.info("Closing applicationContext");
|
||||
applicationContext.close();
|
||||
@@ -390,20 +398,40 @@ public class Server {
|
||||
this.config = conf;
|
||||
}
|
||||
|
||||
private void ensureZkPaths(CuratorFramework curatorFramework, String[] paths) {
|
||||
for (String s : paths) {
|
||||
try {
|
||||
Stat stat = curatorFramework.checkExists().forPath(s);
|
||||
if (stat == null) {
|
||||
curatorFramework.create().creatingParentContainersIfNeeded().withMode(CreateMode.PERSISTENT)
|
||||
.forPath(s);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.info(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void run() throws Exception {
|
||||
|
||||
RetryPolicy retryPolicy = new RetryForever(1000);
|
||||
curatorFramework = CuratorFrameworkFactory.newClient(config.getAccumulo().getZookeepers(), 30000, 10000,
|
||||
retryPolicy);
|
||||
curatorFramework.start();
|
||||
ensureZkPaths(curatorFramework, zkPaths);
|
||||
|
||||
int nettyThreads = Math.max(1,
|
||||
SystemPropertyUtil.getInt("io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));
|
||||
dataStore = DataStoreFactory.create(config, nettyThreads);
|
||||
if (config.getCache().isEnabled()) {
|
||||
dataStoreCache = new DataStoreCache(config);
|
||||
dataStoreCache = new DataStoreCache(curatorFramework, config);
|
||||
dataStoreCache.setInternalMetrics(dataStore.getInternalMetrics());
|
||||
dataStore.setCache(dataStoreCache);
|
||||
}
|
||||
// Initialize the MetaCache
|
||||
MetaCacheFactory.getCache(config);
|
||||
// initialize the auth cache
|
||||
AuthCache.setSessionMaxAge(config);
|
||||
AuthCache.configure(config.getSecurity());
|
||||
// Initialize the VisibilityCache
|
||||
VisibilityCache.init(config);
|
||||
final boolean useEpoll = useEpoll();
|
||||
@@ -500,7 +528,7 @@ public class Server {
|
||||
udpServer.option(EpollChannelOption.SO_REUSEPORT, true);
|
||||
udpChannelHandleList.add(udpServer.bind(udpIp, udpPort).sync().channel());
|
||||
}
|
||||
registerService();
|
||||
registerService(curatorFramework);
|
||||
shutdownHook();
|
||||
LOG.info(
|
||||
"Server started. Listening on {}:{} for TCP traffic, {}:{} for HTTP traffic, {}:{} for WebSocket traffic, and {}:{} for UDP traffic",
|
||||
@@ -560,7 +588,7 @@ public class Server {
|
||||
ch.pipeline().addLast("decoder", new HttpRequestDecoder());
|
||||
ch.pipeline().addLast("compressor", new HttpContentCompressor());
|
||||
ch.pipeline().addLast("decompressor", new HttpContentDecompressor());
|
||||
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(8192));
|
||||
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
|
||||
ch.pipeline().addLast("chunker", new ChunkedWriteHandler());
|
||||
final Cors corsCfg = config.getHttp().getCors();
|
||||
final CorsConfig.Builder ccb;
|
||||
@@ -608,7 +636,7 @@ public class Server {
|
||||
ch.pipeline().addLast("logger", new LoggingHandler());
|
||||
ch.pipeline().addLast("packetDecoder", new UdpPacketToByteBuf());
|
||||
ch.pipeline().addLast("buffer", new MetricsBufferDecoder());
|
||||
ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(8192, true, Delimiters.lineDelimiter()));
|
||||
ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(65536, true, Delimiters.lineDelimiter()));
|
||||
ch.pipeline().addLast("putDecoder", new UdpDecoder());
|
||||
ch.pipeline().addLast("putHandler", new TcpPutHandler(dataStore));
|
||||
}
|
||||
@@ -621,7 +649,7 @@ public class Server {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) throws Exception {
|
||||
ch.pipeline().addLast("buffer", new MetricsBufferDecoder());
|
||||
ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(8192, true, Delimiters.lineDelimiter()));
|
||||
ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(65536, true, Delimiters.lineDelimiter()));
|
||||
ch.pipeline().addLast("putDecoder", new TcpDecoder());
|
||||
ch.pipeline().addLast("putHandler", new TcpPutHandler(dataStore));
|
||||
ch.pipeline().addLast("versionHandler", new TcpVersionHandler());
|
||||
@@ -636,11 +664,11 @@ public class Server {
|
||||
protected void initChannel(SocketChannel ch) throws Exception {
|
||||
ch.pipeline().addLast("ssl", sslCtx.newHandler(ch.alloc()));
|
||||
ch.pipeline().addLast("httpServer", new HttpServerCodec());
|
||||
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(8192));
|
||||
ch.pipeline().addLast("sessionExtractor", new WebSocketHttpCookieHandler(config.getSecurity()));
|
||||
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
|
||||
ch.pipeline().addLast("sessionExtractor", new WebSocketFullRequestHandler());
|
||||
ch.pipeline().addLast("idle-handler", new IdleStateHandler(conf.getWebsocket().getTimeout(), 0, 0));
|
||||
ch.pipeline().addLast("ws-protocol",
|
||||
new WebSocketServerProtocolHandler(WS_PATH, null, true, 65536, true));
|
||||
new WebSocketServerProtocolHandler(WS_PATH, null, true, 65536, false, true));
|
||||
ch.pipeline().addLast("wsDecoder", new WebSocketRequestDecoder(config.getSecurity()));
|
||||
ch.pipeline().addLast("aggregators", new WSAggregatorsRequestHandler());
|
||||
ch.pipeline().addLast("metrics", new WSMetricsRequestHandler(config));
|
||||
@@ -649,7 +677,8 @@ public class Server {
|
||||
ch.pipeline().addLast("suggest", new WSSuggestRequestHandler(dataStore));
|
||||
ch.pipeline().addLast("version", new WSVersionRequestHandler());
|
||||
ch.pipeline().addLast("put", new WSMetricPutHandler(dataStore));
|
||||
ch.pipeline().addLast("create", new WSCreateSubscriptionRequestHandler(dataStore, config));
|
||||
ch.pipeline().addLast("create",
|
||||
new WSCreateSubscriptionRequestHandler(dataStore, dataStoreCache, config));
|
||||
ch.pipeline().addLast("add", new WSAddSubscriptionRequestHandler());
|
||||
ch.pipeline().addLast("remove", new WSRemoveSubscriptionRequestHandler());
|
||||
ch.pipeline().addLast("close", new WSCloseSubscriptionRequestHandler());
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package timely.adapter.accumulo;
|
||||
|
||||
import org.apache.accumulo.core.client.lexicoder.LongLexicoder;
|
||||
import org.apache.accumulo.core.client.lexicoder.PairLexicoder;
|
||||
import org.apache.accumulo.core.client.lexicoder.StringLexicoder;
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import timely.api.model.Meta;
|
||||
import timely.model.Metric;
|
||||
import timely.model.parse.TagListParser;
|
||||
import timely.model.parse.TagParser;
|
||||
|
||||
/**
|
||||
* Creates Accumulo data structures from {@link Metric}
|
||||
*/
|
||||
public class MetaAdapter {
|
||||
|
||||
private static final PairLexicoder<String, Long> rowCoder = new PairLexicoder<>(new StringLexicoder(),
|
||||
new LongLexicoder());
|
||||
|
||||
private static final PairLexicoder<Long, String> colQualCoder = new PairLexicoder<>(new LongLexicoder(),
|
||||
new StringLexicoder());
|
||||
|
||||
private static final TagParser tagParser = new TagParser();
|
||||
private static final TagListParser tagListParser = new TagListParser();
|
||||
|
||||
public static Key createMetricKey(String metricName, Long timestamp) {
|
||||
return new Key(Meta.METRIC_PREFIX + metricName, "", "", timestamp);
|
||||
}
|
||||
|
||||
public static Key createTagKey(String metricName, String tagName, Long timestamp) {
|
||||
return new Key(Meta.TAG_PREFIX + metricName, tagName, "", timestamp);
|
||||
}
|
||||
|
||||
public static Key createValueKey(String metricName, String tagName, String tagValue, Long timestamp) {
|
||||
return new Key(Meta.VALUE_PREFIX + metricName, tagName, tagValue, timestamp);
|
||||
}
|
||||
|
||||
public static Meta parse(Key key) {
|
||||
String row = key.getRow().toString();
|
||||
if (row.startsWith(Meta.METRIC_PREFIX)) {
|
||||
return Meta.parse(key, null, Meta.METRIC_PREFIX);
|
||||
} else if (row.startsWith(Meta.TAG_PREFIX)) {
|
||||
return Meta.parse(key, null, Meta.TAG_PREFIX);
|
||||
} else if (row.startsWith(Meta.VALUE_PREFIX)) {
|
||||
return Meta.parse(key, null, Meta.VALUE_PREFIX);
|
||||
}
|
||||
throw new IllegalStateException("Invalid key in meta " + key.toString());
|
||||
}
|
||||
|
||||
public static String decodeRowKey(Key key) {
|
||||
return parse(key).getMetric();
|
||||
}
|
||||
}
|
||||
@@ -55,16 +55,43 @@ public class Meta implements Comparable<Meta> {
|
||||
return keys;
|
||||
}
|
||||
|
||||
public static Meta parse(Key k, Value v) {
|
||||
public static String prefix(Key key) {
|
||||
String row = key.getRow().toString();
|
||||
if (row.startsWith(Meta.METRIC_PREFIX)) {
|
||||
return Meta.METRIC_PREFIX;
|
||||
} else if (row.startsWith(Meta.TAG_PREFIX)) {
|
||||
return Meta.TAG_PREFIX;
|
||||
} else if (row.startsWith(Meta.VALUE_PREFIX)) {
|
||||
return Meta.VALUE_PREFIX;
|
||||
}
|
||||
throw new IllegalStateException("Invalid key in meta " + key.toString());
|
||||
}
|
||||
|
||||
public static Meta parse(Key key) {
|
||||
return parse(key, null);
|
||||
}
|
||||
|
||||
public static Meta parse(Key key, Value value) {
|
||||
String row = key.getRow().toString();
|
||||
if (row.startsWith(Meta.METRIC_PREFIX)) {
|
||||
return Meta.parse(key, value, Meta.METRIC_PREFIX);
|
||||
} else if (row.startsWith(Meta.TAG_PREFIX)) {
|
||||
return Meta.parse(key, value, Meta.TAG_PREFIX);
|
||||
} else if (row.startsWith(Meta.VALUE_PREFIX)) {
|
||||
return Meta.parse(key, value, Meta.VALUE_PREFIX);
|
||||
}
|
||||
throw new IllegalStateException("Invalid key in meta " + key.toString());
|
||||
}
|
||||
|
||||
public static Meta parse(Key k, Value v, String prefix) {
|
||||
if (k.getColumnQualifier().getLength() > 0) {
|
||||
return new Meta(k.getRow().toString().substring(METRIC_PREFIX.length()), k.getColumnFamily().toString(),
|
||||
return new Meta(k.getRow().toString().substring(prefix.length()), k.getColumnFamily().toString(),
|
||||
k.getColumnQualifier().toString());
|
||||
} else if (k.getColumnFamily().getLength() > 0) {
|
||||
return new Meta(k.getRow().toString().substring(METRIC_PREFIX.length()), k.getColumnFamily().toString(),
|
||||
null);
|
||||
return new Meta(k.getRow().toString().substring(prefix.length()), k.getColumnFamily().toString(), null);
|
||||
|
||||
} else {
|
||||
return new Meta(k.getRow().toString().substring(METRIC_PREFIX.length()), null, null);
|
||||
return new Meta(k.getRow().toString().substring(prefix.length()), null, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
package timely.api.request;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import timely.auth.TimelyUser;
|
||||
import timely.auth.util.HttpHeaderUtils;
|
||||
import timely.netty.http.auth.TimelyAuthenticationToken;
|
||||
|
||||
/**
|
||||
* Base class for requests that require authentication
|
||||
* Base class for requests that require auth
|
||||
*/
|
||||
@JsonIgnoreProperties({ "userName", "requestHeader" })
|
||||
public class AuthenticatedRequest implements Request {
|
||||
|
||||
private String sessionId = null;
|
||||
private Map<String, String> requestHeaders = new HashMap<>();
|
||||
private TimelyAuthenticationToken token = null;
|
||||
private Multimap<String, String> requestHeaders = HashMultimap.create();
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
@@ -22,12 +26,17 @@ public class AuthenticatedRequest implements Request {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public void addHeaders(List<Entry<String, String>> headers) {
|
||||
headers.forEach(l -> requestHeaders.put(l.getKey(), l.getValue()));
|
||||
public void addHeaders(Multimap<String, String> headers) {
|
||||
requestHeaders.putAll(headers);
|
||||
}
|
||||
|
||||
public Map<String, String> getRequestHeaders() {
|
||||
return Collections.unmodifiableMap(requestHeaders);
|
||||
public Multimap<String, String> getRequestHeaders() {
|
||||
return requestHeaders;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String getRequestHeader(String name) {
|
||||
return HttpHeaderUtils.getSingleHeader(requestHeaders, name, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -35,17 +44,32 @@ public class AuthenticatedRequest implements Request {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("{");
|
||||
buf.append("Session ID: ").append(sessionId);
|
||||
buf.append("Token: ").append(token == null ? null : token.getTimelyPrincipal().getName());
|
||||
buf.append(", Request Headers: ").append(requestHeaders.toString());
|
||||
buf.append("}");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() {
|
||||
Request.super.validate();
|
||||
if (null == sessionId) {
|
||||
throw new IllegalArgumentException("SessionID is null, must log in first");
|
||||
}
|
||||
public void setToken(TimelyAuthenticationToken token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public TimelyAuthenticationToken getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String getUserName() {
|
||||
String name;
|
||||
try {
|
||||
TimelyUser user = token.getTimelyPrincipal().getPrimaryUser();
|
||||
name = user.getCommonName();
|
||||
if (name == null) {
|
||||
name = user.getName();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
name = "UNKNOWN";
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -1,81 +1,150 @@
|
||||
package timely.auth;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import org.apache.accumulo.core.security.Authorizations;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.api.request.AuthenticatedRequest;
|
||||
import timely.api.request.Request;
|
||||
import timely.api.response.TimelyException;
|
||||
import timely.configuration.Configuration;
|
||||
import timely.api.request.AuthenticatedWebSocketRequest;
|
||||
import timely.auth.util.AuthorizationsUtil;
|
||||
import timely.configuration.Security;
|
||||
import timely.netty.http.auth.TimelyAuthenticationToken;
|
||||
|
||||
public class AuthCache {
|
||||
|
||||
private static Cache<String, Authentication> CACHE = null;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AuthCache.class);
|
||||
private static Cache<String, TimelyPrincipal> CACHE = null;
|
||||
|
||||
private static int sessionMaxAge = -1;
|
||||
private static int cacheExpirationMinutes = -1;
|
||||
private static int cacheRefreshMinutes = -1;
|
||||
|
||||
/**
|
||||
* For tests only
|
||||
*/
|
||||
public static void resetSessionMaxAge() {
|
||||
sessionMaxAge = -1;
|
||||
public static void resetConfiguration() {
|
||||
cacheExpirationMinutes = -1;
|
||||
cacheRefreshMinutes = -1;
|
||||
}
|
||||
|
||||
public static void setSessionMaxAge(Configuration config) {
|
||||
if (-1 != sessionMaxAge) {
|
||||
throw new IllegalStateException("Cache session max age already configured.");
|
||||
public static void configure(Security security) {
|
||||
if (-1 != cacheExpirationMinutes || -1 != cacheRefreshMinutes) {
|
||||
throw new IllegalStateException("Cache already configured.");
|
||||
}
|
||||
sessionMaxAge = config.getSecurity().getSessionMaxAge();
|
||||
cacheExpirationMinutes = security.getCacheExpirationMinutes();
|
||||
cacheRefreshMinutes = security.getCacheRefreshMinutes();
|
||||
}
|
||||
|
||||
public static Cache<String, Authentication> getCache() {
|
||||
if (-1 == sessionMaxAge) {
|
||||
private static Cache<String, TimelyPrincipal> getCache() {
|
||||
if (-1 == cacheExpirationMinutes) {
|
||||
throw new IllegalStateException("Cache session max age not configured.");
|
||||
}
|
||||
if (null == CACHE) {
|
||||
CACHE = Caffeine.newBuilder().expireAfterAccess(sessionMaxAge, TimeUnit.SECONDS).build();
|
||||
Caffeine<Object, Object> caffeine = Caffeine.newBuilder();
|
||||
caffeine.expireAfterWrite(cacheExpirationMinutes, TimeUnit.MINUTES);
|
||||
if (cacheRefreshMinutes > 0) {
|
||||
caffeine.refreshAfterWrite(cacheRefreshMinutes, TimeUnit.MINUTES);
|
||||
CACHE = caffeine.build(key -> getTimelyPrincipal(key));
|
||||
} else {
|
||||
CACHE = caffeine.build();
|
||||
}
|
||||
}
|
||||
return CACHE;
|
||||
}
|
||||
|
||||
public static Authorizations getAuthorizations(String sessionId) {
|
||||
if (!StringUtils.isEmpty(sessionId)) {
|
||||
Authentication auth = CACHE.asMap().get(sessionId);
|
||||
if (null != auth) {
|
||||
Collection<? extends GrantedAuthority> authorities = CACHE.asMap().get(sessionId).getAuthorities();
|
||||
String[] auths = new String[authorities.size()];
|
||||
final AtomicInteger i = new AtomicInteger(0);
|
||||
authorities.forEach(a -> auths[i.getAndIncrement()] = a.getAuthority());
|
||||
return new Authorizations(auths);
|
||||
private static TimelyPrincipal getTimelyPrincipal(String key) {
|
||||
LOG.debug("Refreshing TimelyPrincipal {}", key);
|
||||
TimelyPrincipal timelyPrincipal = null;
|
||||
String[] dnArray = key.split(" -> ");
|
||||
if (dnArray.length == 1) {
|
||||
SubjectIssuerDNPair pair = SubjectIssuerDNPair.parse(dnArray[0]);
|
||||
TimelyAuthenticationToken token = AuthenticationService.getAuthenticationToken(new Object(),
|
||||
pair.subjectDN(), pair.issuerDN());
|
||||
timelyPrincipal = AuthenticationService.authenticate(token, pair, token.getTimelyPrincipal().getName(),
|
||||
false);
|
||||
} else if (dnArray.length > 1) {
|
||||
Collection<TimelyUser> timelyUsers = new ArrayList<>();
|
||||
for (String s : dnArray) {
|
||||
SubjectIssuerDNPair pair = SubjectIssuerDNPair.parse(s);
|
||||
TimelyAuthenticationToken token = AuthenticationService.getAuthenticationToken(new Object(),
|
||||
pair.subjectDN(), pair.issuerDN());
|
||||
TimelyPrincipal p = AuthenticationService.authenticate(token, pair,
|
||||
token.getTimelyPrincipal().getName(), false);
|
||||
timelyUsers.addAll(p.getProxiedUsers());
|
||||
}
|
||||
timelyPrincipal = new TimelyPrincipal(timelyUsers);
|
||||
}
|
||||
return timelyPrincipal;
|
||||
}
|
||||
|
||||
public static boolean containsKey(String key) {
|
||||
return getCache().getIfPresent(key) != null;
|
||||
}
|
||||
|
||||
public static TimelyPrincipal get(String key) {
|
||||
return getCache().getIfPresent(key);
|
||||
}
|
||||
|
||||
public static void put(String key, TimelyPrincipal principal) {
|
||||
getCache().put(key, principal);
|
||||
}
|
||||
|
||||
public static void remove(String key) {
|
||||
getCache().invalidate(key);
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
if (CACHE != null) {
|
||||
CACHE.invalidateAll();
|
||||
}
|
||||
}
|
||||
|
||||
protected static Collection<Authorizations> getAuthorizations(String entityName) {
|
||||
if (!StringUtils.isEmpty(entityName)) {
|
||||
List<Authorizations> authorizationsList = new ArrayList<>();
|
||||
TimelyPrincipal principal = AuthCache.get(entityName);
|
||||
if (principal != null) {
|
||||
for (Collection<String> authCollection : principal.getAuthorizations()) {
|
||||
authorizationsList.add(AuthorizationsUtil.toAuthorizations(authCollection));
|
||||
}
|
||||
LOG.debug("Authorizations for user {} {}", entityName, principal.getAuthorizationsString());
|
||||
return authorizationsList;
|
||||
} else {
|
||||
return null;
|
||||
return Collections.singletonList(Authorizations.EMPTY);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("session id cannot be null");
|
||||
throw new IllegalArgumentException("entityName can not be null");
|
||||
}
|
||||
}
|
||||
|
||||
public static void enforceAccess(Security security, Request r) throws Exception {
|
||||
if (!security.isAllowAnonymousAccess() && (r instanceof AuthenticatedRequest)) {
|
||||
AuthenticatedRequest ar = (AuthenticatedRequest) r;
|
||||
if (StringUtils.isEmpty(ar.getSessionId())) {
|
||||
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "User must log in",
|
||||
"Anonymous access is disabled, log in first");
|
||||
}
|
||||
if (!AuthCache.getCache().asMap().containsKey(ar.getSessionId())) {
|
||||
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "User must log in",
|
||||
"Unknown session id was submitted, log in again");
|
||||
}
|
||||
public static Collection<Authorizations> getAuthorizations(AuthenticatedRequest request, Security security) {
|
||||
Collection<Authorizations> auths;
|
||||
boolean anonAccessAllowed;
|
||||
if (request instanceof AuthenticatedWebSocketRequest) {
|
||||
anonAccessAllowed = security.isAllowAnonymousWsAccess();
|
||||
} else {
|
||||
anonAccessAllowed = security.isAllowAnonymousHttpAccess();
|
||||
}
|
||||
TimelyAuthenticationToken token = request.getToken();
|
||||
String sessionId = request.getSessionId();
|
||||
if (StringUtils.isBlank(sessionId) && token.getClientCert() == null) {
|
||||
if (anonAccessAllowed) {
|
||||
auths = Collections.singletonList(Authorizations.EMPTY);
|
||||
} else {
|
||||
throw new IllegalArgumentException("User must provide either a sessionId or a client certificate");
|
||||
}
|
||||
} else if (StringUtils.isNotBlank(sessionId)) {
|
||||
auths = getAuthorizations(sessionId);
|
||||
} else {
|
||||
auths = getAuthorizations(token.getTimelyPrincipal().getName());
|
||||
}
|
||||
return auths;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,37 +1,271 @@
|
||||
package timely.auth;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.ServiceConfigurationError;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import timely.api.request.AuthenticatedRequest;
|
||||
import timely.api.response.TimelyException;
|
||||
import timely.auth.util.DnUtils;
|
||||
import timely.netty.http.auth.TimelyAuthenticationToken;
|
||||
import timely.netty.http.auth.TimelyUserDetails;
|
||||
|
||||
public class AuthenticationService {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AuthenticationService.class);
|
||||
private static ApplicationContext springContext = null;
|
||||
private static AuthenticationManager authManager = null;
|
||||
private static SubjectDnX509PrincipalExtractor x509 = null;
|
||||
private static ConcurrentHashMap<String, Object> fetchingEntity = new ConcurrentHashMap<>();
|
||||
private static ConcurrentHashMap<String, Object> fetchingPrincipal = new ConcurrentHashMap<>();
|
||||
private static Collection<String> requiredRoles;
|
||||
private static Collection<String> requiredAuths;
|
||||
|
||||
static {
|
||||
try {
|
||||
springContext = new ClassPathXmlApplicationContext("security.xml");
|
||||
authManager = (AuthenticationManager) springContext.getBean("authenticationManager");
|
||||
x509 = (SubjectDnX509PrincipalExtractor) springContext.getBean("x509PrincipalExtractor");
|
||||
authManager = springContext.getBean("authenticationManager", AuthenticationManager.class);
|
||||
requiredRoles = springContext.getBean("requiredRoles", List.class);
|
||||
requiredRoles.removeIf(String::isEmpty);
|
||||
requiredAuths = springContext.getBean("requiredAuths", List.class);
|
||||
requiredAuths.removeIf(String::isEmpty);
|
||||
} catch (BeansException e) {
|
||||
throw new ServiceConfigurationError("Error setting up Authentication objects: " + e.getMessage(),
|
||||
e.getRootCause());
|
||||
}
|
||||
}
|
||||
|
||||
public static AuthenticationManager getAuthenticationManager() {
|
||||
private static AuthenticationManager getAuthenticationManager() {
|
||||
return authManager;
|
||||
}
|
||||
|
||||
public static String extractDN(X509Certificate clientCert) throws Exception {
|
||||
return (String) x509.extractPrincipal(clientCert);
|
||||
public static TimelyPrincipal authenticate(Authentication authentication, String cacheName) {
|
||||
return authenticate(authentication, SubjectIssuerDNPair.of(cacheName), cacheName);
|
||||
}
|
||||
|
||||
public static TimelyPrincipal authenticate(Authentication authentication, SubjectIssuerDNPair pair, String entity) {
|
||||
return authenticate(authentication, pair, entity, true);
|
||||
}
|
||||
|
||||
public static TimelyPrincipal authenticate(Authentication authentication, SubjectIssuerDNPair pair, String entity,
|
||||
boolean useCache) {
|
||||
|
||||
// only one thread should call the authenticationManager for a given entity
|
||||
TimelyPrincipal principal = null;
|
||||
if (useCache) {
|
||||
principal = AuthCache.get(entity);
|
||||
}
|
||||
// once an entity is in the AuthCache, we can bypass all of this
|
||||
if (principal == null) {
|
||||
Object newObj = new Object();
|
||||
Object o = fetchingEntity.getOrDefault(entity, newObj);
|
||||
if (o == null) {
|
||||
o = newObj;
|
||||
}
|
||||
try {
|
||||
synchronized (o) {
|
||||
// if multiple threads are waiting on this synchronized, then the 2nd and all
|
||||
// others will get the principal here
|
||||
if (useCache) {
|
||||
principal = AuthCache.get(entity);
|
||||
}
|
||||
if (principal == null) {
|
||||
Authentication token = getAuthenticationManager().authenticate(authentication);
|
||||
principal = getTimelyPrincipal(token, pair);
|
||||
LOG.trace("Got entity principal for {} from AuthenticationManager", entity);
|
||||
if (useCache) {
|
||||
AuthCache.put(entity, principal);
|
||||
}
|
||||
} else {
|
||||
LOG.trace("Got entity principal for {} from AuthCache on 2nd attempt", entity);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
fetchingEntity.remove(entity);
|
||||
}
|
||||
} else {
|
||||
LOG.trace("Got entity principal for {} from AuthCache on 1st attempt", entity);
|
||||
}
|
||||
return principal;
|
||||
}
|
||||
|
||||
public static void enforceAccess(AuthenticatedRequest request) throws Exception {
|
||||
String sessionId = request.getSessionId();
|
||||
X509Certificate clientCert = request.getToken().getClientCert();
|
||||
TimelyPrincipal verifiedPrincipal = null;
|
||||
if (StringUtils.isBlank(sessionId) && clientCert == null) {
|
||||
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "User must log in",
|
||||
"Anonymous access is disabled. User must either send a client certificate or log in");
|
||||
} else if (StringUtils.isNotBlank(sessionId)) {
|
||||
verifiedPrincipal = AuthCache.get(sessionId);
|
||||
if (verifiedPrincipal == null) {
|
||||
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "User must log in",
|
||||
"Unknown session id was submitted, log in again");
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
TimelyAuthenticationToken token = request.getToken();
|
||||
TimelyPrincipal requestPrincipal = token.getTimelyPrincipal();
|
||||
String entity = requestPrincipal.getName();
|
||||
verifiedPrincipal = AuthCache.get(entity);
|
||||
if (verifiedPrincipal == null) {
|
||||
Object newObj = new Object();
|
||||
Object o = fetchingPrincipal.getOrDefault(entity, newObj);
|
||||
if (o == null) {
|
||||
o = newObj;
|
||||
}
|
||||
try {
|
||||
synchronized (o) {
|
||||
// check again if in the AuthCache
|
||||
verifiedPrincipal = AuthCache.get(entity);
|
||||
if (verifiedPrincipal == null) {
|
||||
// first thread here, so authenticate all entities in requestPrincipal
|
||||
List<TimelyUser> authenticatedTimelyUsers = new ArrayList<>();
|
||||
for (TimelyUser user : requestPrincipal.getProxiedUsers()) {
|
||||
SubjectIssuerDNPair p = user.getDn();
|
||||
TimelyAuthenticationToken entityToken = getAuthenticationToken(clientCert,
|
||||
p.subjectDN(), p.issuerDN());
|
||||
TimelyPrincipal entityPrincipal = authenticate(entityToken, p,
|
||||
entityToken.getTimelyPrincipal().getName());
|
||||
authenticatedTimelyUsers.addAll(entityPrincipal.getProxiedUsers());
|
||||
}
|
||||
verifiedPrincipal = new TimelyPrincipal(authenticatedTimelyUsers);
|
||||
AuthCache.put(verifiedPrincipal.getName(), verifiedPrincipal);
|
||||
LOG.debug("Authenticated user {} for request {} with authorizations {}",
|
||||
verifiedPrincipal.getName(), request.toString(),
|
||||
verifiedPrincipal.getAuthorizationsString());
|
||||
} else {
|
||||
LOG.trace("Verified principal {} in AuthCache on 2nd attempt", entity);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
fetchingPrincipal.remove(entity);
|
||||
}
|
||||
} else {
|
||||
LOG.trace("Verified principal {} in AuthCache on 1st attempt", entity);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "Access denied", e.getMessage());
|
||||
}
|
||||
}
|
||||
try {
|
||||
checkAllowedAccess(verifiedPrincipal.getPrimaryUser());
|
||||
} catch (TimelyException e) {
|
||||
// validation based on roles and/or auths failed
|
||||
// remove combined principal and primary user from cache
|
||||
// i.e. don't cache a failure
|
||||
AuthCache.remove(verifiedPrincipal.getName());
|
||||
AuthCache.remove(verifiedPrincipal.getPrimaryUser().getName());
|
||||
throw e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void checkAllowedAccess(TimelyUser primaryUser) throws TimelyException {
|
||||
Set<String> missingRoles = new TreeSet<>();
|
||||
Set<String> missingAuths = new TreeSet<>();
|
||||
if (requiredRoles != null) {
|
||||
if (!primaryUser.getRoles().containsAll(requiredRoles)) {
|
||||
missingRoles.addAll(requiredRoles);
|
||||
missingRoles.removeAll(primaryUser.getRoles());
|
||||
}
|
||||
}
|
||||
if (requiredAuths != null) {
|
||||
if (!primaryUser.getAuths().containsAll(requiredAuths)) {
|
||||
missingAuths.addAll(requiredRoles);
|
||||
missingAuths.removeAll(primaryUser.getAuths());
|
||||
}
|
||||
}
|
||||
|
||||
if (!missingRoles.isEmpty() || !missingAuths.isEmpty()) {
|
||||
String message = "";
|
||||
if (!missingRoles.isEmpty() && !missingAuths.isEmpty()) {
|
||||
message = "User:" + primaryUser.getName() + " is missing role(s):" + missingRoles + " and auth(s):"
|
||||
+ missingAuths;
|
||||
|
||||
} else if (!missingRoles.isEmpty()) {
|
||||
message = "User:" + primaryUser.getName() + " is missing role(s):" + missingRoles;
|
||||
} else if (missingAuths.isEmpty()) {
|
||||
message = "User:" + primaryUser.getName() + " is missing auths(s):" + missingAuths;
|
||||
}
|
||||
LOG.debug(message);
|
||||
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "Access denied", message);
|
||||
}
|
||||
}
|
||||
|
||||
public static X509Certificate getClientCertificate(ChannelHandlerContext ctx) {
|
||||
X509Certificate clientCert = null;
|
||||
if (ctx != null) {
|
||||
SslHandler sslHandler = (SslHandler) ctx.channel().pipeline().get("ssl");
|
||||
if (null != sslHandler) {
|
||||
try {
|
||||
clientCert = (X509Certificate) sslHandler.engine().getSession().getPeerCertificates()[0];
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("The expected SSL handler is not in the pipeline.");
|
||||
}
|
||||
}
|
||||
return clientCert;
|
||||
}
|
||||
|
||||
public static TimelyAuthenticationToken getAuthenticationToken(X509Certificate clientCert,
|
||||
Multimap<String, String> headers) {
|
||||
TimelyAuthenticationToken authenticationToken = null;
|
||||
try {
|
||||
authenticationToken = new TimelyAuthenticationToken(clientCert.getSubjectDN().getName(), clientCert,
|
||||
headers);
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
return authenticationToken;
|
||||
}
|
||||
|
||||
public static TimelyAuthenticationToken getAuthenticationToken(Object clientCert, String subjectDn,
|
||||
String issuerDn) {
|
||||
TimelyAuthenticationToken authenticationToken = null;
|
||||
try {
|
||||
authenticationToken = new TimelyAuthenticationToken(subjectDn, issuerDn, clientCert);
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
return authenticationToken;
|
||||
}
|
||||
|
||||
private static TimelyPrincipal getTimelyPrincipal(Authentication authentication,
|
||||
SubjectIssuerDNPair subjectIssuerDNPair) {
|
||||
String subjectDn = subjectIssuerDNPair.subjectDN();
|
||||
TimelyUser.UserType userType = DnUtils.isServerDN(subjectDn) ? TimelyUser.UserType.SERVER
|
||||
: TimelyUser.UserType.USER;
|
||||
Collection<String> auths = authentication.getAuthorities().stream().map(a -> a.getAuthority())
|
||||
.collect(Collectors.toList());
|
||||
Object o = authentication.getPrincipal();
|
||||
Collection<String> roles = null;
|
||||
if (o instanceof TimelyUserDetails) {
|
||||
roles = ((TimelyUserDetails) o).getRoles();
|
||||
}
|
||||
TimelyUser timelyUser = new TimelyUser(subjectIssuerDNPair, userType, auths, roles, null,
|
||||
System.currentTimeMillis());
|
||||
return new TimelyPrincipal(Arrays.asList(timelyUser));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package timely.auth;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.accumulo.core.security.Authorizations;
|
||||
|
||||
/**
|
||||
* @see AuthorizationsMinimizer#minimize(Collection)
|
||||
*/
|
||||
public class AuthorizationsMinimizer {
|
||||
|
||||
/**
|
||||
* Given a collection of Accumulo {@link Authorizations} objects, this method
|
||||
* attempts to compute a "minimum" set that can be used for a multi-entity scan
|
||||
* of data from Accumulo. The idea is that there is a call chain with multiple
|
||||
* entities (e.g., a middleware broker acting on behalf of a GUI that is in turn
|
||||
* acting on behalf of a user). In order to ensure that only data visible to all
|
||||
* of the entities is returned, we must add a visibility filter for each entity
|
||||
* in the chain to ensure that each key/value pair returned by Accumulo can be
|
||||
* seen by all of the entities in the chain. Since we may be scanning a very
|
||||
* large number of keys, it would be ideal if we could reduce the number of
|
||||
* visibility filters to a small set if that can be done safely.
|
||||
* <p>
|
||||
* The crux of the problem, and the reason we need multiple filters in the first
|
||||
* place (as opposed to just intersecting the authorizations for all all
|
||||
* entities and using that set), is the fact that Accumulo visibility
|
||||
* expressions may include disjunctions. Consider the following visibility
|
||||
* expression.
|
||||
* <p>
|
||||
* {@code (A & B & ORG1) | (A & C & ORG2)}
|
||||
* <p>
|
||||
* If the incoming user has the Accumulo authorizations A, B, and ORG1 and the
|
||||
* user is coming through a server with the Accumulo authorizations A, C, and
|
||||
* ORG2, then the server is allowed to see data protected with this expression
|
||||
* and the user is also allowed, so the data should be returned with those two
|
||||
* calling entities. If we simply computed an intersection of the authorizations
|
||||
* that set would be A. That set of authorizations does not match the visibility
|
||||
* expression and data that should be returned would not be.
|
||||
* <p>
|
||||
* Given that we must apply multiple visibility filters in order to properly
|
||||
* return data for a calling entity chain, it is desirable to minimize the
|
||||
* number of visibility filters used, if possible. If a set of authorizations is
|
||||
* a superset of any other set of authorizations, then it does not need to be
|
||||
* tested. As long as we are not changing any of the authorizations sets, we
|
||||
* know we will never allow any data to be returned that would otherwise not be.
|
||||
* By removing authorizations sets, we might only prevent the return of data
|
||||
* that should otherwise be returned. Consider a given set of authorizations
|
||||
* that is a subset of another. If the subset passes a visibility expression,
|
||||
* then the superset will too since it contains at least the same
|
||||
* authorizations, so there is no need to test against it. If the subset does
|
||||
* not pass, then the data is not visible to the chain anyway, so there is no
|
||||
* point in testing the superset.
|
||||
*
|
||||
* @param authorizations
|
||||
* the list of Authorizations for each entity in the call chain
|
||||
* @return a minimized set of Authorizations that allows visibility of exactly
|
||||
* the same data as {@code authorizations}
|
||||
*/
|
||||
public static Collection<Authorizations> minimize(Collection<Authorizations> authorizations) {
|
||||
if (authorizations.size() > 1) {
|
||||
// Convert collection of Authorizations into a collection of String sets (the
|
||||
// individual authorizations).
|
||||
// Since we are adding to a LinkedHashSet, this will de-dupe any duplicate
|
||||
// authorization sets.
|
||||
final LinkedHashSet<Set<String>> allAuths = authorizations.stream().map(
|
||||
a -> a.getAuthorizations().stream().map(String::new).collect(Collectors.toCollection(HashSet::new)))
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
|
||||
// Go through the authorizations sets and remove any that are supersets of any
|
||||
// other.
|
||||
for (Iterator<Set<String>> it = allAuths.iterator(); it.hasNext(); /* empty */) {
|
||||
Set<String> currentSet = it.next();
|
||||
if (allAuths.stream().filter(a -> a != currentSet && a.size() <= currentSet.size())
|
||||
.anyMatch(currentSet::containsAll))
|
||||
it.remove();
|
||||
}
|
||||
|
||||
// If we removed any sets of authorizations, then we need to convert the reduced
|
||||
// set from
|
||||
// TreeSet<String> objects back into Authorizations objects.
|
||||
if (allAuths.size() < authorizations.size()) {
|
||||
authorizations = allAuths.stream().map(a -> new Authorizations(a.toArray(new String[0])))
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
}
|
||||
}
|
||||
return authorizations;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package timely.auth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
import org.apache.accumulo.core.iterators.IteratorEnvironment;
|
||||
import org.apache.accumulo.core.iterators.OptionDescriber;
|
||||
import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
|
||||
import org.apache.accumulo.core.iterators.WrappingIterator;
|
||||
import org.apache.accumulo.core.iterators.system.VisibilityFilter;
|
||||
import org.apache.accumulo.core.security.Authorizations;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
public class ConfigurableVisibilityFilter extends WrappingIterator implements OptionDescriber {
|
||||
|
||||
public static final String AUTHORIZATIONS_OPT = "authorizations";
|
||||
private SortedKeyValueIterator<Key, Value> delegate;
|
||||
|
||||
private static final Logger log = Logger.getLogger(ConfigurableVisibilityFilter.class);
|
||||
|
||||
public ConfigurableVisibilityFilter() {
|
||||
}
|
||||
|
||||
public ConfigurableVisibilityFilter(SortedKeyValueIterator<Key, Value> iterator, Authorizations authorizations,
|
||||
byte[] defaultVisibility) {
|
||||
setSource(iterator);
|
||||
}
|
||||
|
||||
public ConfigurableVisibilityFilter(ConfigurableVisibilityFilter other, IteratorEnvironment env) {
|
||||
this.delegate = other.delegate.deepCopy(env);
|
||||
this.setSource(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(SortedKeyValueIterator<Key, Value> source, Map<String, String> options, IteratorEnvironment env)
|
||||
throws IOException {
|
||||
Authorizations auths = Authorizations.EMPTY;
|
||||
if (options.containsKey(AUTHORIZATIONS_OPT))
|
||||
auths = new Authorizations(options.get(AUTHORIZATIONS_OPT).split(","));
|
||||
log.debug("Using authorizations: " + auths);
|
||||
|
||||
delegate = VisibilityFilter.wrap(source, auths, new byte[0]);
|
||||
super.init(delegate, options, env);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedKeyValueIterator<Key, Value> deepCopy(IteratorEnvironment env) {
|
||||
return new ConfigurableVisibilityFilter(this, env);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IteratorOptions describeOptions() {
|
||||
IteratorOptions opts = new IteratorOptions(getClass().getSimpleName(),
|
||||
"Filters keys based to return only those whose visibility tests positive against the supplied authorizations",
|
||||
null, null);
|
||||
opts.addNamedOption(AUTHORIZATIONS_OPT, "Comma delimited list of scan authorizations");
|
||||
return opts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateOptions(Map<String, String> options) {
|
||||
boolean valid = false;
|
||||
String auths = options.get(AUTHORIZATIONS_OPT);
|
||||
if (auths != null) {
|
||||
try {
|
||||
new Authorizations(auths.split(","));
|
||||
valid = true;
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
package timely.auth;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.apache.accumulo.core.client.IteratorSetting;
|
||||
import org.apache.accumulo.core.client.IteratorSetting.Column;
|
||||
import org.apache.accumulo.core.client.ScannerBase;
|
||||
import org.apache.accumulo.core.client.impl.ScannerOptions;
|
||||
import org.apache.accumulo.core.client.sample.SamplerConfiguration;
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
import org.apache.accumulo.core.data.thrift.IterInfo;
|
||||
import org.apache.accumulo.core.security.Authorizations;
|
||||
import org.apache.hadoop.io.Text;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A simple wrapper around a {@link ScannerBase} that overrides the methods that
|
||||
* configure iterators.
|
||||
*
|
||||
* @see ScannerOptionsHelper
|
||||
*/
|
||||
public class ScannerBaseDelegate implements ScannerBase {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ScannerBaseDelegate.class);
|
||||
private static final String SYSTEM_ITERATOR_NAME_PREFIX = "sys_";
|
||||
|
||||
protected final ScannerBase delegate;
|
||||
|
||||
public ScannerBaseDelegate(ScannerBase delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addScanIterator(IteratorSetting cfg) {
|
||||
if (cfg.getName().startsWith(SYSTEM_ITERATOR_NAME_PREFIX)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Non-system iterators' names cannot start with " + SYSTEM_ITERATOR_NAME_PREFIX);
|
||||
} else {
|
||||
delegate.addScanIterator(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a "system" scan iterator. The iterator name is automatically prefixed
|
||||
* with {@link #SYSTEM_ITERATOR_NAME_PREFIX}. A "system" scan iterator can only
|
||||
* be modified or removed by calling
|
||||
* {@link #updateSystemScanIteratorOption(String, String, String)},
|
||||
* {@link #removeSystemScanIterator(String)}, or
|
||||
* {@link #clearSystemScanIterators()}. Updates the iterator configuration for
|
||||
* {@code iteratorName}. The iterator name is automatically prefixed with
|
||||
* {@link #SYSTEM_ITERATOR_NAME_PREFIX}.
|
||||
*
|
||||
* @param cfg
|
||||
* the configuration of the iterator to add
|
||||
*/
|
||||
public void addSystemScanIterator(IteratorSetting cfg) {
|
||||
if (!cfg.getName().startsWith(SYSTEM_ITERATOR_NAME_PREFIX)) {
|
||||
cfg.setName(SYSTEM_ITERATOR_NAME_PREFIX + cfg.getName());
|
||||
}
|
||||
delegate.addScanIterator(cfg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScanIterator(String iteratorName) {
|
||||
if (iteratorName.startsWith(SYSTEM_ITERATOR_NAME_PREFIX)) {
|
||||
throw new IllegalArgumentException("DATAWAVE system iterator " + iteratorName + " cannot be removed");
|
||||
} else {
|
||||
delegate.removeScanIterator(iteratorName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a "system" scan iterator. The iterator name is automatically prefixed
|
||||
* with {@link #SYSTEM_ITERATOR_NAME_PREFIX}.
|
||||
*
|
||||
* @param iteratorName
|
||||
* the name of the system iterator to remove
|
||||
*/
|
||||
public void removeSystemScanIterator(String iteratorName) {
|
||||
if (!iteratorName.startsWith(SYSTEM_ITERATOR_NAME_PREFIX)) {
|
||||
iteratorName = SYSTEM_ITERATOR_NAME_PREFIX + iteratorName;
|
||||
}
|
||||
delegate.removeScanIterator(iteratorName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateScanIteratorOption(String iteratorName, String key, String value) {
|
||||
if (iteratorName.startsWith(SYSTEM_ITERATOR_NAME_PREFIX)) {
|
||||
throw new IllegalArgumentException("DATAWAVE system iterator " + iteratorName + " cannot be updated");
|
||||
} else {
|
||||
delegate.updateScanIteratorOption(iteratorName, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the iterator configuration for {@code iteratorName}. The iterator
|
||||
* name is automatically prefixed with {@link #SYSTEM_ITERATOR_NAME_PREFIX}.
|
||||
*
|
||||
* @param iteratorName
|
||||
* the name of the system iterator to modify
|
||||
* @param key
|
||||
* the name of the iterator option to modify
|
||||
* @param value
|
||||
* the new value for the iterator option named in {@code key}
|
||||
*/
|
||||
public void updateSystemScanIteratorOption(String iteratorName, String key, String value) {
|
||||
if (!iteratorName.startsWith(SYSTEM_ITERATOR_NAME_PREFIX)) {
|
||||
iteratorName = SYSTEM_ITERATOR_NAME_PREFIX + iteratorName;
|
||||
}
|
||||
delegate.updateScanIteratorOption(iteratorName, key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchColumnFamily(Text col) {
|
||||
delegate.fetchColumnFamily(col);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchColumn(Text colFam, Text colQual) {
|
||||
delegate.fetchColumn(colFam, colQual);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchColumn(Column column) {
|
||||
delegate.fetchColumn(column);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearColumns() {
|
||||
delegate.clearColumns();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearScanIterators() {
|
||||
if (delegate instanceof ScannerOptions) {
|
||||
ScannerOptionsHelper opts = new ScannerOptionsHelper((ScannerOptions) delegate);
|
||||
for (IteratorSetting iteratorSetting : opts.getIterators()) {
|
||||
if (!iteratorSetting.getName().startsWith(SYSTEM_ITERATOR_NAME_PREFIX)) {
|
||||
delegate.removeScanIterator(iteratorSetting.getName());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new UnsupportedOperationException(
|
||||
"Cannot clear scan iterators on a non-ScannerOptions class! (" + delegate.getClass() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all iterators (including system iterators).
|
||||
*/
|
||||
public void clearSystemScanIterators() {
|
||||
delegate.clearScanIterators();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Map.Entry<Key, Value>> iterator() {
|
||||
return delegate.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTimeout(long timeOut, TimeUnit timeUnit) {
|
||||
delegate.setTimeout(timeOut, timeUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeout(TimeUnit timeUnit) {
|
||||
return delegate.getTimeout(timeUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
delegate.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authorizations getAuthorizations() {
|
||||
return delegate.getAuthorizations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSamplerConfiguration(SamplerConfiguration samplerConfiguration) {
|
||||
delegate.setSamplerConfiguration(samplerConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SamplerConfiguration getSamplerConfiguration() {
|
||||
return delegate.getSamplerConfiguration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearSamplerConfiguration() {
|
||||
delegate.clearSamplerConfiguration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchTimeout(long l, TimeUnit timeUnit) {
|
||||
delegate.setBatchTimeout(l, timeUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBatchTimeout(TimeUnit timeUnit) {
|
||||
return delegate.getBatchTimeout(timeUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClassLoaderContext(String s) {
|
||||
delegate.setClassLoaderContext(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearClassLoaderContext() {
|
||||
delegate.clearClassLoaderContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassLoaderContext() {
|
||||
return delegate.getClassLoaderContext();
|
||||
}
|
||||
|
||||
public void setContext(String context) {
|
||||
delegate.setClassLoaderContext(context);
|
||||
}
|
||||
|
||||
public void clearContext() {
|
||||
delegate.clearClassLoaderContext();
|
||||
}
|
||||
|
||||
public String getContext() {
|
||||
return delegate.getClassLoaderContext();
|
||||
}
|
||||
|
||||
private static class ScannerOptionsHelper extends ScannerOptions {
|
||||
|
||||
public ScannerOptionsHelper(ScannerOptions other) {
|
||||
super(other);
|
||||
}
|
||||
|
||||
public Collection<IteratorSetting> getIterators() {
|
||||
Collection<IteratorSetting> settings = Lists.newArrayList();
|
||||
for (IterInfo iter : serverSideIteratorList) {
|
||||
IteratorSetting setting = new IteratorSetting(iter.getPriority(), iter.getIterName(),
|
||||
iter.getClassName());
|
||||
setting.addOptions(serverSideIteratorOptions.get(iter.getIterName()));
|
||||
settings.add(setting);
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package timely.auth;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import timely.auth.util.ProxiedEntityUtils;
|
||||
|
||||
/**
|
||||
* A simple pair containing a subject and (optional) issuer DN. The supplied DN
|
||||
* values are normalized into a lower-case form with the CN portion first.
|
||||
*/
|
||||
public class SubjectIssuerDNPair implements Serializable {
|
||||
|
||||
private final String subjectDN;
|
||||
private final String issuerDN;
|
||||
|
||||
public static SubjectIssuerDNPair of(String subjectDN) {
|
||||
return new SubjectIssuerDNPair(subjectDN, null);
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
public static SubjectIssuerDNPair of(@JsonProperty("subjectDN") String subjectDN,
|
||||
@JsonProperty("issuerDN") String issuerDN) {
|
||||
return new SubjectIssuerDNPair(subjectDN, issuerDN);
|
||||
}
|
||||
|
||||
public static SubjectIssuerDNPair parse(String dn) {
|
||||
String[] dns = ProxiedEntityUtils.splitProxiedSubjectIssuerDNs(dn);
|
||||
if (dns.length != 2)
|
||||
throw new IllegalArgumentException(dn + " must contain a single subject and issuer DN");
|
||||
return new SubjectIssuerDNPair(dns[0], dns[1]);
|
||||
}
|
||||
|
||||
protected SubjectIssuerDNPair(String subjectDN, String issuerDN) {
|
||||
this.subjectDN = ProxiedEntityUtils.normalizeDN(subjectDN);
|
||||
if (issuerDN != null) {
|
||||
this.issuerDN = ProxiedEntityUtils.normalizeDN(issuerDN);
|
||||
} else {
|
||||
this.issuerDN = null;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonGetter
|
||||
public String subjectDN() {
|
||||
return subjectDN;
|
||||
}
|
||||
|
||||
@JsonGetter
|
||||
public String issuerDN() {
|
||||
return issuerDN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return issuerDN == null ? subjectDN + "<>" : ProxiedEntityUtils.buildProxiedDN(subjectDN, issuerDN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
SubjectIssuerDNPair that = (SubjectIssuerDNPair) o;
|
||||
|
||||
if (!subjectDN.equals(that.subjectDN))
|
||||
return false;
|
||||
return issuerDN != null ? issuerDN.equals(that.issuerDN) : that.issuerDN == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = subjectDN.hashCode();
|
||||
result = 31 * result + (issuerDN != null ? issuerDN.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package timely.auth;
|
||||
|
||||
import static timely.auth.TimelyUser.ANONYMOUS_USER;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.security.Principal;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
|
||||
import timely.auth.util.DnUtils;
|
||||
|
||||
/**
|
||||
* A {@link Principal} that represents a set of proxied {@link TimelyUser}s. For
|
||||
* example, this proxied user could represent a GUI server acting on behalf of a
|
||||
* user. The GUI server user represents the entity that made the call to us and
|
||||
* the other proxied user would be the actual end user.
|
||||
*/
|
||||
@XmlRootElement
|
||||
@XmlType(factoryMethod = "anonymousPrincipal", propOrder = { "name", "proxiedUsers", "creationTime" })
|
||||
@XmlAccessorType(XmlAccessType.NONE)
|
||||
public class TimelyPrincipal implements Principal, Serializable {
|
||||
|
||||
private final String username;
|
||||
private final TimelyUser primaryUser;
|
||||
@XmlElement
|
||||
private final Set<TimelyUser> proxiedUsers = new LinkedHashSet<>();
|
||||
@XmlElement
|
||||
private final long creationTime;
|
||||
|
||||
/**
|
||||
* This constructor should not be used. It is here to allow JAX-B mapping and
|
||||
* CDI proxying of this class.
|
||||
*/
|
||||
public TimelyPrincipal() {
|
||||
this(ANONYMOUS_USER);
|
||||
}
|
||||
|
||||
public TimelyPrincipal(TimelyUser timelyUser) {
|
||||
this.creationTime = System.currentTimeMillis();
|
||||
this.primaryUser = timelyUser;
|
||||
this.proxiedUsers.add(timelyUser);
|
||||
this.username = timelyUser.getName();
|
||||
}
|
||||
|
||||
public TimelyPrincipal(Collection<TimelyUser> proxiedUsers) {
|
||||
this(proxiedUsers, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public TimelyPrincipal(Collection<TimelyUser> proxiedUsers, long creationTime) {
|
||||
this.creationTime = creationTime;
|
||||
TimelyUser first = proxiedUsers.stream().findFirst().orElse(null);
|
||||
this.primaryUser = proxiedUsers.stream().filter(u -> u.getUserType() == TimelyUser.UserType.USER).findFirst()
|
||||
.orElse(first);
|
||||
this.proxiedUsers.add(primaryUser);
|
||||
this.proxiedUsers
|
||||
.addAll(proxiedUsers.stream().filter(u -> !u.equals(primaryUser)).collect(Collectors.toList()));
|
||||
this.username = this.proxiedUsers.stream().map(TimelyUser::getName).collect(Collectors.joining(" -> "));
|
||||
}
|
||||
|
||||
public Set<TimelyUser> getProxiedUsers() {
|
||||
return Collections.unmodifiableSet(proxiedUsers);
|
||||
}
|
||||
|
||||
public TimelyUser getPrimaryUser() {
|
||||
return primaryUser;
|
||||
}
|
||||
|
||||
public Collection<? extends Collection<String>> getAuthorizations() {
|
||||
return Collections
|
||||
.unmodifiableCollection(proxiedUsers.stream().map(TimelyUser::getAuths).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
public String[] getDNs() {
|
||||
return proxiedUsers.stream().map(TimelyUser::getDn).map(SubjectIssuerDNPair::subjectDN).toArray(String[]::new);
|
||||
}
|
||||
|
||||
public long getCreationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
@XmlElement
|
||||
public String getName() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getShortName() {
|
||||
return DnUtils.getShortName(getPrimaryUser().getName());
|
||||
}
|
||||
|
||||
public SubjectIssuerDNPair getUserDN() {
|
||||
return getPrimaryUser().getDn();
|
||||
}
|
||||
|
||||
public Set<String> getProxyServers() {
|
||||
|
||||
// @formatter:off
|
||||
Set<String> proxyServers = getProxiedUsers().stream()
|
||||
.filter(u -> u.getUserType() == TimelyUser.UserType.SERVER)
|
||||
.map(TimelyUser::getDn)
|
||||
.map(SubjectIssuerDNPair::subjectDN)
|
||||
.collect(Collectors.toSet());
|
||||
// @formatter:on
|
||||
return proxyServers.isEmpty() ? null : proxyServers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
TimelyPrincipal that = (TimelyPrincipal) o;
|
||||
|
||||
if (!username.equals(that.username))
|
||||
return false;
|
||||
return proxiedUsers.equals(that.proxiedUsers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = username.hashCode();
|
||||
result = 31 * result + proxiedUsers.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TimelyPrincipal{" + "name='" + username + "'" + ", proxiedUsers=" + proxiedUsers + "}";
|
||||
}
|
||||
|
||||
public static TimelyPrincipal anonymousPrincipal() {
|
||||
return new TimelyPrincipal(ANONYMOUS_USER);
|
||||
}
|
||||
|
||||
public String getAuthorizationsString() {
|
||||
return proxiedUsers.stream().map(TimelyUser::getAuths).map(a -> a.toString())
|
||||
.collect(Collectors.joining(" -> ", "[", "]"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package timely.auth;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.collect.LinkedHashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import timely.auth.util.DnUtils;
|
||||
|
||||
/**
|
||||
* A user of a TIMELY service. Typically, one or more of these users (a chain
|
||||
* where a user called an intermediate service which in turn called us) is
|
||||
* represented with a TimelyPrincipal.
|
||||
*/
|
||||
public class TimelyUser implements Serializable {
|
||||
|
||||
public enum UserType {
|
||||
USER, SERVER
|
||||
}
|
||||
|
||||
public static final TimelyUser ANONYMOUS_USER = new TimelyUser(SubjectIssuerDNPair.of("ANONYMOUS"), UserType.USER,
|
||||
null, null, null, -1L);
|
||||
private final String name;
|
||||
private final String commonName;
|
||||
private final SubjectIssuerDNPair dn;
|
||||
private final UserType userType;
|
||||
private final Collection<String> auths;
|
||||
private final Collection<String> unmodifiableAuths;
|
||||
private final Collection<String> roles;
|
||||
private final Collection<String> unmodifiableRoles;
|
||||
private final Multimap<String, String> roleToAuthMapping;
|
||||
private final long creationTime;
|
||||
private final long expirationTime;
|
||||
|
||||
/*
|
||||
* For Spring configuration
|
||||
*/
|
||||
public TimelyUser(String subjectDn, Collection<String> auths, Collection<String> roles) {
|
||||
this.name = subjectDn;
|
||||
this.commonName = DnUtils.getCommonName(subjectDn);
|
||||
this.dn = SubjectIssuerDNPair.of(subjectDn);
|
||||
this.userType = DnUtils.isServerDN(subjectDn) ? UserType.SERVER : UserType.USER;
|
||||
this.auths = auths == null ? Collections.emptyList() : new LinkedHashSet<>(auths);
|
||||
this.unmodifiableAuths = Collections.unmodifiableCollection(this.auths);
|
||||
this.roles = roles == null ? Collections.emptyList() : new LinkedHashSet<>(roles);
|
||||
this.unmodifiableRoles = Collections.unmodifiableCollection(this.roles);
|
||||
this.roleToAuthMapping = null;
|
||||
this.creationTime = System.currentTimeMillis();
|
||||
this.expirationTime = -1L;
|
||||
}
|
||||
|
||||
/*
|
||||
* For testing
|
||||
*/
|
||||
public TimelyUser(String subjectDn, Collection<String> auths) {
|
||||
this(SubjectIssuerDNPair.of(subjectDn), UserType.USER, auths, null, null, System.currentTimeMillis(), -1L);
|
||||
}
|
||||
|
||||
public TimelyUser(SubjectIssuerDNPair dn, UserType userType, Collection<String> auths, Collection<String> roles,
|
||||
Multimap<String, String> roleToAuthMapping, long creationTime) {
|
||||
this(dn, userType, auths, roles, roleToAuthMapping, creationTime, -1L);
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
public TimelyUser(@JsonProperty(value = "dn", required = true) SubjectIssuerDNPair dn,
|
||||
@JsonProperty(value = "userType", required = true) UserType userType,
|
||||
@JsonProperty("auths") Collection<String> auths, @JsonProperty("roles") Collection<String> roles,
|
||||
@JsonProperty("roleToAuthMapping") Multimap<String, String> roleToAuthMapping,
|
||||
@JsonProperty(value = "creationTime", defaultValue = "-1L") long creationTime,
|
||||
@JsonProperty(value = "expirationTime", defaultValue = "-1L") long expirationTime) {
|
||||
this.name = dn.toString();
|
||||
this.commonName = DnUtils.getCommonName(dn.subjectDN());
|
||||
this.dn = dn;
|
||||
this.userType = userType;
|
||||
this.auths = auths == null ? Collections.emptyList() : new LinkedHashSet<>(auths);
|
||||
this.unmodifiableAuths = Collections.unmodifiableCollection(this.auths);
|
||||
this.roles = roles == null ? Collections.emptyList() : new LinkedHashSet<>(roles);
|
||||
this.unmodifiableRoles = Collections.unmodifiableCollection(this.roles);
|
||||
this.roleToAuthMapping = roleToAuthMapping == null ? LinkedHashMultimap.create()
|
||||
: Multimaps.unmodifiableMultimap(LinkedHashMultimap.create(roleToAuthMapping));
|
||||
this.creationTime = creationTime;
|
||||
this.expirationTime = expirationTime;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String getCommonName() {
|
||||
return commonName;
|
||||
}
|
||||
|
||||
public SubjectIssuerDNPair getDn() {
|
||||
return dn;
|
||||
}
|
||||
|
||||
public UserType getUserType() {
|
||||
return userType;
|
||||
}
|
||||
|
||||
public Collection<String> getAuths() {
|
||||
return unmodifiableAuths;
|
||||
}
|
||||
|
||||
public Collection<String> getRoles() {
|
||||
return unmodifiableRoles;
|
||||
}
|
||||
|
||||
public Multimap<String, String> getRoleToAuthMapping() {
|
||||
return roleToAuthMapping;
|
||||
}
|
||||
|
||||
public long getCreationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
public long getExpirationTime() {
|
||||
return expirationTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
TimelyUser that = (TimelyUser) o;
|
||||
|
||||
return creationTime == that.creationTime && dn.equals(that.dn) && userType == that.userType
|
||||
&& auths.equals(that.auths) && roles.equals(that.roles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = dn.hashCode();
|
||||
result = 31 * result + userType.hashCode();
|
||||
result = 31 * result + auths.hashCode();
|
||||
result = 31 * result + roles.hashCode();
|
||||
result = 31 * result + (int) (creationTime ^ (creationTime >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TimelyUser{" + "name='" + getName() + "'" + ", userType=" + getUserType() + ", auths=" + getAuths()
|
||||
+ ", roles=" + getRoles() + ", creationTime=" + getCreationTime() + "}";
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package timely.auth;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -11,18 +10,20 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
import timely.netty.http.auth.TimelyUserDetails;
|
||||
|
||||
public class UserDetailsService implements
|
||||
org.springframework.security.core.userdetails.AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {
|
||||
|
||||
private HashMap<String, List<String>> users;
|
||||
private HashMap<String, TimelyUser> users;
|
||||
|
||||
public Map<String, List<String>> getUsers() {
|
||||
public Map<String, TimelyUser> getUsers() {
|
||||
return users;
|
||||
}
|
||||
|
||||
public void setUsers(LinkedHashMap<String, List<String>> users) {
|
||||
this.users = new HashMap<>(users);
|
||||
public void setUsers(List<TimelyUser> users) {
|
||||
this.users = new HashMap<>();
|
||||
users.forEach(u -> this.users.put(u.getName(), u));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -30,25 +31,32 @@ public class UserDetailsService implements
|
||||
throws UsernameNotFoundException {
|
||||
// Determine if the user is allowed to access the system, if not throw
|
||||
// UsernameNotFoundException
|
||||
String username = token.getName().toString();
|
||||
String username = token.getName();
|
||||
if (!users.containsKey(username)) {
|
||||
throw new UsernameNotFoundException(username + " not configured.");
|
||||
}
|
||||
// If allowed, populate the user details object with the authorities for
|
||||
// the user.
|
||||
return new UserDetails() {
|
||||
return new TimelyUserDetails() {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Collection<SimpleGrantedAuthority> getAuthorities() {
|
||||
final Collection<SimpleGrantedAuthority> auths = new ArrayList<>();
|
||||
users.get(username).forEach(a -> {
|
||||
users.get(username).getAuths().forEach(a -> {
|
||||
auths.add(new SimpleGrantedAuthority(a));
|
||||
});
|
||||
return auths;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getRoles() {
|
||||
final Collection<String> roles = new ArrayList<>();
|
||||
roles.addAll(users.get(username).getRoles());
|
||||
return roles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return "";
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package timely.auth.util;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.apache.accumulo.core.security.Authorizations;
|
||||
import timely.auth.AuthorizationsMinimizer;
|
||||
|
||||
public class AuthorizationsUtil {
|
||||
|
||||
public static Authorizations union(Iterable<byte[]> authorizations1, Iterable<byte[]> authorizations2) {
|
||||
LinkedList<byte[]> aggregatedAuthorizations = Lists.newLinkedList();
|
||||
addTo(aggregatedAuthorizations, authorizations1);
|
||||
addTo(aggregatedAuthorizations, authorizations2);
|
||||
return new Authorizations(aggregatedAuthorizations);
|
||||
}
|
||||
|
||||
private static void addTo(LinkedList<byte[]> aggregatedAuthorizations, Iterable<byte[]> authsToAdd) {
|
||||
for (byte[] auth : authsToAdd) {
|
||||
aggregatedAuthorizations.add(auth);
|
||||
}
|
||||
}
|
||||
|
||||
public static Authorizations toAuthorizations(Collection<String> auths) {
|
||||
return new Authorizations(auths.stream().map(String::trim).map(s -> s.getBytes(Charset.forName("UTF-8")))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
public static List<String> splitAuths(String requestedAuths) {
|
||||
return Arrays.asList(Iterables.toArray(Splitter.on(',').omitEmptyStrings().trimResults().split(requestedAuths),
|
||||
String.class));
|
||||
}
|
||||
|
||||
public static Set<Authorizations> buildAuthorizations(Collection<? extends Collection<String>> userAuths) {
|
||||
if (null == userAuths) {
|
||||
return Collections.singleton(new Authorizations());
|
||||
}
|
||||
|
||||
HashSet<Authorizations> auths = Sets.newHashSet();
|
||||
for (Collection<String> userAuth : userAuths) {
|
||||
auths.add(new Authorizations(userAuth.toArray(new String[userAuth.size()])));
|
||||
}
|
||||
|
||||
return auths;
|
||||
}
|
||||
|
||||
public static Collection<Authorizations> minimize(Collection<Authorizations> authorizations) {
|
||||
return AuthorizationsMinimizer.minimize(authorizations);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package timely.auth.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DnUtils {
|
||||
|
||||
/** Config for injecting NPE OU identifiers */
|
||||
public static final String PROPS_RESOURCE = "dnutils.properties";
|
||||
|
||||
private static final Properties PROPS = new Properties();
|
||||
|
||||
public static final String SUBJECT_DN_PATTERN_PROPERTY = "subject.dn.pattern";
|
||||
|
||||
private static final Pattern SUBJECT_DN_PATTERN;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DnUtils.class);
|
||||
|
||||
static {
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = DnUtils.class.getClassLoader().getResourceAsStream(PROPS_RESOURCE);
|
||||
PROPS.load(in);
|
||||
} catch (Throwable t) {
|
||||
log.error(PROPS_RESOURCE + " could not be loaded!", t);
|
||||
throw new RuntimeException(t);
|
||||
} finally {
|
||||
if (null != in) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
log.warn("Failed to close input stream", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
String subjectDnPattern = System.getProperty(SUBJECT_DN_PATTERN_PROPERTY,
|
||||
PROPS.getProperty(SUBJECT_DN_PATTERN_PROPERTY));
|
||||
try {
|
||||
if (null == subjectDnPattern || subjectDnPattern.isEmpty()) {
|
||||
throw new IllegalStateException(SUBJECT_DN_PATTERN_PROPERTY + " property value cannot be null");
|
||||
}
|
||||
SUBJECT_DN_PATTERN = Pattern.compile(subjectDnPattern, Pattern.CASE_INSENSITIVE);
|
||||
} catch (Throwable t) {
|
||||
log.error(SUBJECT_DN_PATTERN_PROPERTY + " = '" + subjectDnPattern + "' could not be compiled", t);
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
public static String[] splitProxiedDNs(String proxiedDNs, boolean allowDups) {
|
||||
return ProxiedEntityUtils.splitProxiedDNs(proxiedDNs, allowDups);
|
||||
}
|
||||
|
||||
public static String[] splitProxiedSubjectIssuerDNs(String proxiedDNs) {
|
||||
return ProxiedEntityUtils.splitProxiedSubjectIssuerDNs(proxiedDNs);
|
||||
}
|
||||
|
||||
public static String buildProxiedDN(String... dns) {
|
||||
return ProxiedEntityUtils.buildProxiedDN(dns);
|
||||
}
|
||||
|
||||
public static Collection<String> buildNormalizedDNList(String subjectDN, String issuerDN, String proxiedSubjectDNs,
|
||||
String proxiedIssuerDNs) {
|
||||
subjectDN = normalizeDN(subjectDN);
|
||||
issuerDN = normalizeDN(issuerDN);
|
||||
List<String> dnList = new ArrayList<>();
|
||||
HashSet<String> subjects = new HashSet<>();
|
||||
String subject = subjectDN.replaceAll("(?<!\\\\)([<>])", "\\\\$1");
|
||||
dnList.add(subject);
|
||||
subjects.add(subject);
|
||||
dnList.add(issuerDN.replaceAll("(?<!\\\\)([<>])", "\\\\$1"));
|
||||
if (proxiedSubjectDNs != null) {
|
||||
if (proxiedIssuerDNs == null)
|
||||
throw new IllegalArgumentException(
|
||||
"If proxied subject DNs are supplied, then issuer DNs must be supplied as well.");
|
||||
String[] subjectDNarray = splitProxiedDNs(proxiedSubjectDNs, true);
|
||||
String[] issuerDNarray = splitProxiedDNs(proxiedIssuerDNs, true);
|
||||
if (subjectDNarray.length != issuerDNarray.length)
|
||||
throw new IllegalArgumentException(
|
||||
"Subject and issuer DN lists do not have the same number of entries: "
|
||||
+ Arrays.toString(subjectDNarray) + " vs " + Arrays.toString(issuerDNarray));
|
||||
for (int i = 0; i < subjectDNarray.length; ++i) {
|
||||
subjectDNarray[i] = normalizeDN(subjectDNarray[i]);
|
||||
if (!subjects.contains(subjectDNarray[i])) {
|
||||
issuerDNarray[i] = normalizeDN(issuerDNarray[i]);
|
||||
subjects.add(subjectDNarray[i]);
|
||||
dnList.add(subjectDNarray[i]);
|
||||
dnList.add(issuerDNarray[i]);
|
||||
if (issuerDNarray[i].equalsIgnoreCase(subjectDNarray[i]))
|
||||
throw new IllegalArgumentException(
|
||||
"Subject DN " + issuerDNarray[i] + " was passed as an issuer DN.");
|
||||
if (SUBJECT_DN_PATTERN.matcher(issuerDNarray[i]).find())
|
||||
throw new IllegalArgumentException(
|
||||
"It appears that a subject DN (" + issuerDNarray[i] + ") was passed as an issuer DN.");
|
||||
}
|
||||
}
|
||||
}
|
||||
return dnList;
|
||||
}
|
||||
|
||||
public static String buildNormalizedProxyDN(String subjectDN, String issuerDN, String proxiedSubjectDNs,
|
||||
String proxiedIssuerDNs) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String escapedDN : buildNormalizedDNList(subjectDN, issuerDN, proxiedSubjectDNs, proxiedIssuerDNs)) {
|
||||
if (sb.length() == 0)
|
||||
sb.append(escapedDN);
|
||||
else
|
||||
sb.append('<').append(escapedDN).append('>');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String getCommonName(String dn) {
|
||||
return ProxiedEntityUtils.getCommonName(dn);
|
||||
}
|
||||
|
||||
public static String[] getOrganizationalUnits(String dn) {
|
||||
return ProxiedEntityUtils.getOrganizationalUnits(dn);
|
||||
}
|
||||
|
||||
public static String getShortName(String dn) {
|
||||
return ProxiedEntityUtils.getShortName(dn);
|
||||
}
|
||||
|
||||
public static boolean isServerDN(String dn) {
|
||||
return NpeUtils.isNPE(dn);
|
||||
}
|
||||
|
||||
public static String getUserDN(String[] dns) {
|
||||
return getUserDN(dns, false);
|
||||
}
|
||||
|
||||
public static String getUserDN(String[] dns, boolean issuerDNs) {
|
||||
if (issuerDNs && (dns.length % 2) != 0)
|
||||
throw new IllegalArgumentException("DNs array is not a subject/issuer DN list: " + Arrays.toString(dns));
|
||||
|
||||
for (int i = 0; i < dns.length; i += (issuerDNs) ? 2 : 1) {
|
||||
String dn = dns[i];
|
||||
if (!isServerDN(dn))
|
||||
return dn;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String[] getComponents(String dn, String componentName) {
|
||||
return ProxiedEntityUtils.getComponents(dn, componentName);
|
||||
}
|
||||
|
||||
public static String normalizeDN(String userName) {
|
||||
return ProxiedEntityUtils.normalizeDN(userName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates the known/valid NPE OU list, and whatever else is needed for NPE
|
||||
* handling. Static inner-class, so that injection of the configured OUs is only
|
||||
* performed on-demand/if needed
|
||||
*/
|
||||
public static class NpeUtils {
|
||||
|
||||
/** Property containing a comma-delimited list of OUs */
|
||||
public static final String NPE_OU_PROPERTY = "npe.ou.entries";
|
||||
|
||||
/** Parsed NPE OU identifiers */
|
||||
static final List<String> NPE_OU_LIST;
|
||||
|
||||
private NpeUtils() {
|
||||
}
|
||||
|
||||
static {
|
||||
List<String> npeOUs = new ArrayList<>();
|
||||
String ouString = System.getProperty(NPE_OU_PROPERTY, PROPS.getProperty(NPE_OU_PROPERTY));
|
||||
if (null == ouString || ouString.isEmpty()) {
|
||||
throw new IllegalStateException("No '" + NPE_OU_PROPERTY + "' value has been configured");
|
||||
}
|
||||
// Normalize and load...
|
||||
String[] ouArray = ouString.split(",");
|
||||
for (String ou : ouArray) {
|
||||
npeOUs.add(ou.trim().toUpperCase());
|
||||
}
|
||||
NPE_OU_LIST = Collections.unmodifiableList(npeOUs);
|
||||
}
|
||||
|
||||
static boolean isNPE(String dn) {
|
||||
String[] ouList = ProxiedEntityUtils.getOrganizationalUnits(dn);
|
||||
for (String ou : ouList) {
|
||||
if (NPE_OU_LIST.contains(ou.toUpperCase())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
QueryStringDecoder decoder = new QueryStringDecoder("https://my.server:3000");
|
||||
|
||||
System.out.println(decoder.path());
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package timely.auth.util;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
|
||||
public class HttpHeaderUtils {
|
||||
|
||||
public static Multimap<String, String> toMultimap(HttpHeaders headers) {
|
||||
Multimap<String, String> headerMultimap = HashMultimap.create();
|
||||
if (headers != null) {
|
||||
for (Map.Entry<String, String> e : headers.entries()) {
|
||||
headerMultimap.put(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
return headerMultimap;
|
||||
}
|
||||
|
||||
public static String getSingleHeader(Multimap<String, String> headers, String headerName, boolean enforceOneValue)
|
||||
throws IllegalArgumentException {
|
||||
String value = null;
|
||||
Collection<String> values = (headers == null) ? null : headers.get(headerName);
|
||||
if (values != null && !values.isEmpty()) {
|
||||
if (values.size() > 1 && enforceOneValue) {
|
||||
throw new IllegalArgumentException(headerName + " was specified multiple times, which is not allowed");
|
||||
}
|
||||
value = values.stream().findFirst().orElse(null);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package timely.auth.util;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.naming.InvalidNameException;
|
||||
import javax.naming.ldap.LdapName;
|
||||
import javax.naming.ldap.Rdn;
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.netty.http.auth.TimelyAuthenticationToken;
|
||||
|
||||
public class ProxiedEntityUtils {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ProxiedEntityUtils.class);
|
||||
|
||||
public static String[] splitProxiedDNs(String proxiedDNs, boolean allowDups) {
|
||||
String[] dns;
|
||||
if (proxiedDNs.indexOf('<') < 0) {
|
||||
dns = new String[] { proxiedDNs };
|
||||
} else {
|
||||
Collection<String> dnCollection = allowDups ? new ArrayList<>() : new LinkedHashSet<>();
|
||||
String[] pieces = proxiedDNs.split("(?<!\\\\)<|(?<!\\\\)>");
|
||||
for (String piece : pieces) {
|
||||
if (piece.trim().length() > 0)
|
||||
dnCollection.add(piece);
|
||||
}
|
||||
|
||||
dns = dnCollection.toArray(new String[0]);
|
||||
}
|
||||
return dns;
|
||||
}
|
||||
|
||||
public static String[] splitProxiedSubjectIssuerDNs(String proxiedDNs) {
|
||||
String[] dns;
|
||||
if (proxiedDNs.indexOf('<') < 0) {
|
||||
dns = new String[] { proxiedDNs };
|
||||
} else {
|
||||
HashSet<String> subjects = new HashSet<>();
|
||||
List<String> dnList = new ArrayList<>();
|
||||
String[] pieces = proxiedDNs.split("(?<!\\\\)<|(?<!\\\\)>");
|
||||
ArrayList<String> trimmedPieces = new ArrayList<>(pieces.length);
|
||||
for (String piece : pieces) {
|
||||
piece = piece.trim();
|
||||
if (piece.length() > 0)
|
||||
trimmedPieces.add(piece);
|
||||
}
|
||||
if ((trimmedPieces.size() % 2) != 0)
|
||||
throw new IllegalArgumentException("Invalid proxied DNs list does not have entries in pairs.");
|
||||
for (int i = 0; i < trimmedPieces.size(); i += 2) {
|
||||
String subject = trimmedPieces.get(i);
|
||||
String issuer = trimmedPieces.get(i + 1);
|
||||
if (subject.length() > 0 && !subjects.contains(subject)) {
|
||||
subjects.add(subject);
|
||||
dnList.add(subject);
|
||||
dnList.add(issuer);
|
||||
}
|
||||
}
|
||||
|
||||
dns = dnList.toArray(new String[0]);
|
||||
}
|
||||
return dns;
|
||||
}
|
||||
|
||||
public static String buildProxiedDN(String... dns) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String dn : dns) {
|
||||
String escapedDN = dn.replaceAll("(?<!\\\\)(<|>)", "\\\\$1");
|
||||
if (sb.length() == 0)
|
||||
sb.append(escapedDN);
|
||||
else
|
||||
sb.append('<').append(escapedDN).append('>');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String getCommonName(String dn) {
|
||||
String[] comps = getComponents(dn, "CN");
|
||||
return comps.length >= 1 ? comps[0] : null;
|
||||
}
|
||||
|
||||
public static String[] getOrganizationalUnits(String dn) {
|
||||
return getComponents(dn, "OU");
|
||||
}
|
||||
|
||||
public static String getShortName(String dn) {
|
||||
String cn = getCommonName(dn);
|
||||
if (cn == null)
|
||||
cn = dn;
|
||||
String sid = cn;
|
||||
int idx = cn.lastIndexOf(' ');
|
||||
if (idx >= 0)
|
||||
sid = cn.substring(idx + 1);
|
||||
return sid;
|
||||
}
|
||||
|
||||
public static String[] getComponents(String dn, String componentName) {
|
||||
componentName = componentName.toUpperCase();
|
||||
ArrayList<String> components = new ArrayList<>();
|
||||
try {
|
||||
LdapName name = new LdapName(dn);
|
||||
for (Rdn rdn : name.getRdns()) {
|
||||
if (componentName.equals(rdn.getType().toUpperCase())) {
|
||||
components.add(String.valueOf(rdn.getValue()));
|
||||
}
|
||||
}
|
||||
} catch (InvalidNameException e) {
|
||||
// ignore -- invalid name, so can't find components
|
||||
}
|
||||
return components.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to normalize a DN by taking it and reversing the components if it
|
||||
* doesn't start with CN. Some systems requires the DN components be in a
|
||||
* specific order, or that order reversed. We cannot arbitrarily reorder the
|
||||
* components however, e.g., sorting them.
|
||||
*/
|
||||
public static String normalizeDN(String userName) {
|
||||
String normalizedUserName = userName.trim().toLowerCase();
|
||||
try {
|
||||
if (!normalizedUserName.startsWith("cn") || Pattern.compile(",[^ ]").matcher(normalizedUserName).find()) {
|
||||
LdapName name = new LdapName(userName);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
ArrayList<Rdn> rdns = new ArrayList<>(name.getRdns());
|
||||
if (rdns.size() > 0 && !rdns.get(0).toString().toLowerCase().startsWith("cn"))
|
||||
Collections.reverse(rdns);
|
||||
for (Rdn rdn : rdns) {
|
||||
if (sb.length() > 0)
|
||||
sb.append(", ");
|
||||
sb.append(rdn.toString());
|
||||
}
|
||||
normalizedUserName = sb.toString().toLowerCase();
|
||||
}
|
||||
} catch (InvalidNameException e) {
|
||||
// ignore -- might be a sid rather than a DN
|
||||
}
|
||||
log.trace("Normalized [" + userName + "] into [" + normalizedUserName + "]");
|
||||
return normalizedUserName;
|
||||
}
|
||||
|
||||
public static void addProxyHeaders(Multimap<String, String> headers, X509Certificate clientCert) {
|
||||
if (clientCert != null) {
|
||||
List<String> proxiedEntitiesList = new ArrayList<>();
|
||||
if (headers.containsKey(TimelyAuthenticationToken.PROXIED_ENTITIES_HEADER)) {
|
||||
String proxiedEntities = HttpHeaderUtils.getSingleHeader(headers,
|
||||
TimelyAuthenticationToken.PROXIED_ENTITIES_HEADER, false);
|
||||
headers.removeAll(TimelyAuthenticationToken.PROXIED_ENTITIES_HEADER);
|
||||
String[] preExistingProxiedEntities = ProxiedEntityUtils.splitProxiedDNs(proxiedEntities, false);
|
||||
if (preExistingProxiedEntities.length > 0) {
|
||||
proxiedEntitiesList.addAll(Arrays.asList(preExistingProxiedEntities));
|
||||
}
|
||||
}
|
||||
String subjectDN = DnUtils.normalizeDN(clientCert.getSubjectDN().getName());
|
||||
proxiedEntitiesList.add(subjectDN);
|
||||
String[] newProxiedEntities = new String[proxiedEntitiesList.size()];
|
||||
proxiedEntitiesList.toArray(newProxiedEntities);
|
||||
headers.put(TimelyAuthenticationToken.PROXIED_ENTITIES_HEADER,
|
||||
ProxiedEntityUtils.buildProxiedDN(newProxiedEntities));
|
||||
|
||||
List<String> proxiedIssuersList = new ArrayList<>();
|
||||
if (headers.containsKey(TimelyAuthenticationToken.PROXIED_ISSUERS_HEADER)) {
|
||||
String proxiedIssuers = HttpHeaderUtils.getSingleHeader(headers,
|
||||
TimelyAuthenticationToken.PROXIED_ISSUERS_HEADER, false);
|
||||
headers.removeAll(TimelyAuthenticationToken.PROXIED_ISSUERS_HEADER);
|
||||
String[] preExistingProxiedIssuers = ProxiedEntityUtils.splitProxiedDNs(proxiedIssuers, false);
|
||||
if (preExistingProxiedIssuers.length > 0) {
|
||||
proxiedEntitiesList.addAll(Arrays.asList(preExistingProxiedIssuers));
|
||||
}
|
||||
}
|
||||
String issuerDN = DnUtils.normalizeDN(clientCert.getIssuerDN().getName());
|
||||
proxiedIssuersList.add(issuerDN);
|
||||
String[] newProxiedIssuers = new String[proxiedIssuersList.size()];
|
||||
proxiedIssuersList.toArray(newProxiedIssuers);
|
||||
headers.put(TimelyAuthenticationToken.PROXIED_ISSUERS_HEADER,
|
||||
ProxiedEntityUtils.buildProxiedDN(newProxiedIssuers));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package timely.auth.util;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.accumulo.core.client.BatchDeleter;
|
||||
import org.apache.accumulo.core.client.BatchScanner;
|
||||
import org.apache.accumulo.core.client.BatchWriterConfig;
|
||||
import org.apache.accumulo.core.client.Connector;
|
||||
import org.apache.accumulo.core.client.IteratorSetting;
|
||||
import org.apache.accumulo.core.client.Scanner;
|
||||
import org.apache.accumulo.core.client.ScannerBase;
|
||||
import org.apache.accumulo.core.client.TableNotFoundException;
|
||||
import org.apache.accumulo.core.security.Authorizations;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.auth.AuthorizationsMinimizer;
|
||||
import timely.auth.ConfigurableVisibilityFilter;
|
||||
import timely.auth.ScannerBaseDelegate;
|
||||
|
||||
public class ScannerHelper {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ScannerHelper.class);
|
||||
|
||||
public static Scanner createScanner(Connector connector, String tableName,
|
||||
Collection<Authorizations> authorizations) throws TableNotFoundException {
|
||||
if (authorizations == null || authorizations.isEmpty())
|
||||
throw new IllegalArgumentException("Authorizations must not be empty.");
|
||||
|
||||
Iterator<Authorizations> iter = AuthorizationsMinimizer.minimize(authorizations).iterator();
|
||||
Scanner scanner = connector.createScanner(tableName, iter.next());
|
||||
addVisibilityFilters(iter, scanner);
|
||||
return scanner;
|
||||
}
|
||||
|
||||
public static BatchScanner createBatchScanner(Connector connector, String tableName,
|
||||
Collection<Authorizations> authorizations, int numQueryThreads) throws TableNotFoundException {
|
||||
if (authorizations == null || authorizations.isEmpty())
|
||||
throw new IllegalArgumentException("Authorizations must not be empty.");
|
||||
|
||||
Iterator<Authorizations> iter = AuthorizationsMinimizer.minimize(authorizations).iterator();
|
||||
BatchScanner batchScanner = connector.createBatchScanner(tableName, iter.next(), numQueryThreads);
|
||||
addVisibilityFilters(iter, batchScanner);
|
||||
return batchScanner;
|
||||
}
|
||||
|
||||
public static BatchDeleter createBatchDeleter(Connector connector, String tableName,
|
||||
Collection<Authorizations> authorizations, int numQueryThreads, long maxMemory, long maxLatency,
|
||||
int maxWriteThreads) throws TableNotFoundException {
|
||||
if (authorizations == null || authorizations.isEmpty())
|
||||
throw new IllegalArgumentException("Authorizations must not be empty.");
|
||||
|
||||
Iterator<Authorizations> iter = AuthorizationsMinimizer.minimize(authorizations).iterator();
|
||||
BatchWriterConfig bwCfg = new BatchWriterConfig().setMaxLatency(maxLatency, TimeUnit.MILLISECONDS)
|
||||
.setMaxMemory(maxMemory).setMaxWriteThreads(maxWriteThreads);
|
||||
BatchDeleter batchDeleter = connector.createBatchDeleter(tableName, iter.next(), numQueryThreads, bwCfg);
|
||||
addVisibilityFilters(iter, batchDeleter);
|
||||
return batchDeleter;
|
||||
}
|
||||
|
||||
protected static void addVisibilityFilters(Iterator<Authorizations> iter, ScannerBase scanner) {
|
||||
for (int priority = 10; iter.hasNext(); priority++) {
|
||||
IteratorSetting cfg = new IteratorSetting(priority, ConfigurableVisibilityFilter.class);
|
||||
cfg.setName("visibilityFilter" + priority);
|
||||
cfg.addOption(ConfigurableVisibilityFilter.AUTHORIZATIONS_OPT, iter.next().toString());
|
||||
// Set the visibility filter as a "system" iterator, which means that normal
|
||||
// modify, remove, clear operations performed
|
||||
// on the scanner will not modify/remove/clear this iterator. This way, if a
|
||||
// query logic attempts to reconfigure the
|
||||
// scanner's iterators, then this iterator will remain intact.
|
||||
if (scanner instanceof ScannerBaseDelegate) {
|
||||
((ScannerBaseDelegate) scanner).addSystemScanIterator(cfg);
|
||||
} else {
|
||||
logger.warn("Adding system visibility filter to non-wrapped scanner {}.", scanner.getClass(),
|
||||
new Exception());
|
||||
scanner.addScanIterator(cfg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,9 @@ import javax.validation.Valid;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
@Validated
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "timely")
|
||||
public class Configuration {
|
||||
@@ -19,6 +21,7 @@ public class Configuration {
|
||||
private HashMap<String, Integer> metricAgeOffDays = new HashMap<>();
|
||||
private List<String> metricsReportIgnoredTags = new ArrayList<>();
|
||||
private String instance = null;
|
||||
private String defaultVisibility = null;
|
||||
|
||||
@Valid
|
||||
@NestedConfigurationProperty
|
||||
@@ -71,6 +74,14 @@ public class Configuration {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public String getDefaultVisibility() {
|
||||
return defaultVisibility;
|
||||
}
|
||||
|
||||
public void setDefaultVisibility(String defaultVisibility) {
|
||||
this.defaultVisibility = defaultVisibility;
|
||||
}
|
||||
|
||||
public HashMap<String, Integer> getMetricAgeOffDays() {
|
||||
return metricAgeOffDays;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ package timely.configuration;
|
||||
|
||||
public class MetaCache {
|
||||
|
||||
private long expirationMinutes = 60;
|
||||
private int initialCapacity = 2000;
|
||||
private long maxCapacity = 10000;
|
||||
private long expirationMinutes = 1440;
|
||||
private int cacheRefreshMinutes = -1;
|
||||
private long maxTagValues = 100;
|
||||
|
||||
public long getExpirationMinutes() {
|
||||
return expirationMinutes;
|
||||
@@ -14,19 +14,19 @@ public class MetaCache {
|
||||
this.expirationMinutes = expirationMinutes;
|
||||
}
|
||||
|
||||
public int getInitialCapacity() {
|
||||
return initialCapacity;
|
||||
public void setCacheRefreshMinutes(int cacheRefreshMinutes) {
|
||||
this.cacheRefreshMinutes = cacheRefreshMinutes;
|
||||
}
|
||||
|
||||
public void setInitialCapacity(int initialCapacity) {
|
||||
this.initialCapacity = initialCapacity;
|
||||
public int getCacheRefreshMinutes() {
|
||||
return cacheRefreshMinutes;
|
||||
}
|
||||
|
||||
public long getMaxCapacity() {
|
||||
return maxCapacity;
|
||||
public long getMaxTagValues() {
|
||||
return maxTagValues;
|
||||
}
|
||||
|
||||
public void setMaxCapacity(long maxCapacity) {
|
||||
this.maxCapacity = maxCapacity;
|
||||
public void setMaxTagValues(long maxTagValues) {
|
||||
this.maxTagValues = maxTagValues;
|
||||
}
|
||||
}
|
||||
@@ -6,18 +6,29 @@ import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
|
||||
public class Security {
|
||||
|
||||
private boolean allowAnonymousAccess = false;
|
||||
private boolean allowAnonymousHttpAccess = false;
|
||||
private boolean allowAnonymousWsAccess = false;
|
||||
private int sessionMaxAge = 86400;
|
||||
private int cacheExpirationMinutes = 60;
|
||||
private int cacheRefreshMinutes = 5;
|
||||
@Valid
|
||||
@NestedConfigurationProperty
|
||||
private ServerSsl serverSsl = new ServerSsl();
|
||||
|
||||
public boolean isAllowAnonymousAccess() {
|
||||
return allowAnonymousAccess;
|
||||
public boolean isAllowAnonymousHttpAccess() {
|
||||
return allowAnonymousHttpAccess;
|
||||
}
|
||||
|
||||
public void setAllowAnonymousAccess(boolean allowAnonymousAccess) {
|
||||
this.allowAnonymousAccess = allowAnonymousAccess;
|
||||
public void setAllowAnonymousHttpAccess(boolean allowAnonymousHttpAccess) {
|
||||
this.allowAnonymousHttpAccess = allowAnonymousHttpAccess;
|
||||
}
|
||||
|
||||
public boolean isAllowAnonymousWsAccess() {
|
||||
return allowAnonymousWsAccess;
|
||||
}
|
||||
|
||||
public void setAllowAnonymousWsAccess(boolean allowAnonymousWsAccess) {
|
||||
this.allowAnonymousWsAccess = allowAnonymousWsAccess;
|
||||
}
|
||||
|
||||
public int getSessionMaxAge() {
|
||||
@@ -28,6 +39,22 @@ public class Security {
|
||||
this.sessionMaxAge = sessionMaxAge;
|
||||
}
|
||||
|
||||
public void setCacheExpirationMinutes(int cacheExpirationMinutes) {
|
||||
this.cacheExpirationMinutes = cacheExpirationMinutes;
|
||||
}
|
||||
|
||||
public int getCacheExpirationMinutes() {
|
||||
return cacheExpirationMinutes;
|
||||
}
|
||||
|
||||
public void setCacheRefreshMinutes(int cacheRefreshMinutes) {
|
||||
this.cacheRefreshMinutes = cacheRefreshMinutes;
|
||||
}
|
||||
|
||||
public int getCacheRefreshMinutes() {
|
||||
return cacheRefreshMinutes;
|
||||
}
|
||||
|
||||
public ServerSsl getServerSsl() {
|
||||
return serverSsl;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package timely.netty.http;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
@@ -12,6 +15,7 @@ import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.api.annotation.AnnotationResolver;
|
||||
@@ -22,16 +26,20 @@ import timely.api.request.timeseries.HttpRequest;
|
||||
import timely.api.response.StrictTransportResponse;
|
||||
import timely.api.response.TimelyException;
|
||||
import timely.auth.AuthCache;
|
||||
import timely.auth.AuthenticationService;
|
||||
import timely.auth.SubjectIssuerDNPair;
|
||||
import timely.auth.TimelyPrincipal;
|
||||
import timely.auth.util.HttpHeaderUtils;
|
||||
import timely.configuration.Http;
|
||||
import timely.configuration.Security;
|
||||
import timely.netty.Constants;
|
||||
import timely.netty.http.auth.TimelyAuthenticationToken;
|
||||
|
||||
public class HttpRequestDecoder extends MessageToMessageDecoder<FullHttpRequest> implements TimelyHttpHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpRequestDecoder.class);
|
||||
private static final String LOG_RECEIVED_REQUEST = "Received HTTP request {}";
|
||||
private static final String LOG_PARSED_REQUEST = "Parsed request {}";
|
||||
private static final String NO_AUTHORIZATIONS = "";
|
||||
|
||||
private final Security security;
|
||||
private final String nonSecureRedirectAddress;
|
||||
@@ -42,9 +50,11 @@ public class HttpRequestDecoder extends MessageToMessageDecoder<FullHttpRequest>
|
||||
this.nonSecureRedirectAddress = http.getRedirectPath();
|
||||
}
|
||||
|
||||
public static String getSessionId(FullHttpRequest msg, boolean anonymousAccessAllowed) {
|
||||
public static String getSessionId(FullHttpRequest msg) {
|
||||
Multimap<String, String> headers = HttpHeaderUtils.toMultimap(msg.headers());
|
||||
Collection<String> cookies = headers.get(Names.COOKIE);
|
||||
final StringBuilder buf = new StringBuilder();
|
||||
msg.headers().getAll(Names.COOKIE).forEach(h -> {
|
||||
cookies.forEach(h -> {
|
||||
ServerCookieDecoder.STRICT.decode(h).forEach(c -> {
|
||||
if (c.name().equals(Constants.COOKIE_NAME)) {
|
||||
if (buf.length() == 0) {
|
||||
@@ -53,13 +63,11 @@ public class HttpRequestDecoder extends MessageToMessageDecoder<FullHttpRequest>
|
||||
}
|
||||
});
|
||||
});
|
||||
String sessionId = buf.toString();
|
||||
if (sessionId.length() == 0 && anonymousAccessAllowed) {
|
||||
sessionId = NO_AUTHORIZATIONS;
|
||||
} else if (sessionId.length() == 0) {
|
||||
sessionId = null;
|
||||
if (buf.length() == 0) {
|
||||
return null;
|
||||
} else {
|
||||
return buf.toString();
|
||||
}
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -74,7 +82,7 @@ public class HttpRequestDecoder extends MessageToMessageDecoder<FullHttpRequest>
|
||||
return;
|
||||
}
|
||||
|
||||
final String sessionId = getSessionId(msg, this.security.isAllowAnonymousAccess());
|
||||
final String sessionId = getSessionId(msg);
|
||||
LOG.trace("SessionID: " + sessionId);
|
||||
|
||||
HttpRequest request;
|
||||
@@ -97,9 +105,28 @@ public class HttpRequestDecoder extends MessageToMessageDecoder<FullHttpRequest>
|
||||
LOG.warn("Unhandled HTTP request type {}", msg.getMethod());
|
||||
throw e;
|
||||
}
|
||||
if (request instanceof AuthenticatedRequest && sessionId != null) {
|
||||
((AuthenticatedRequest) request).setSessionId(sessionId);
|
||||
((AuthenticatedRequest) request).addHeaders(msg.headers().entries());
|
||||
if (request instanceof AuthenticatedRequest) {
|
||||
Multimap<String, String> headers = HttpHeaderUtils.toMultimap(msg.headers());
|
||||
((AuthenticatedRequest) request).addHeaders(headers);
|
||||
X509Certificate clientCert = AuthenticationService.getClientCertificate(ctx);
|
||||
TimelyAuthenticationToken token;
|
||||
if (StringUtils.isNotBlank(sessionId)) {
|
||||
TimelyPrincipal principal;
|
||||
((AuthenticatedRequest) request).setSessionId(sessionId);
|
||||
if (AuthCache.containsKey(sessionId)) {
|
||||
principal = AuthCache.get(sessionId);
|
||||
} else {
|
||||
principal = TimelyPrincipal.anonymousPrincipal();
|
||||
}
|
||||
SubjectIssuerDNPair dn = principal.getPrimaryUser().getDn();
|
||||
token = AuthenticationService.getAuthenticationToken(null, dn.subjectDN(), dn.issuerDN());
|
||||
} else if (clientCert != null) {
|
||||
token = AuthenticationService.getAuthenticationToken(clientCert, headers);
|
||||
} else {
|
||||
SubjectIssuerDNPair dn = TimelyPrincipal.anonymousPrincipal().getPrimaryUser().getDn();
|
||||
token = AuthenticationService.getAuthenticationToken(null, dn.subjectDN(), dn.issuerDN());
|
||||
}
|
||||
((AuthenticatedRequest) request).setToken(token);
|
||||
}
|
||||
request.setHttpRequest(msg.copy());
|
||||
LOG.trace(LOG_PARSED_REQUEST, request);
|
||||
@@ -113,7 +140,15 @@ public class HttpRequestDecoder extends MessageToMessageDecoder<FullHttpRequest>
|
||||
return;
|
||||
}
|
||||
try {
|
||||
AuthCache.enforceAccess(security, request);
|
||||
if (request instanceof AuthenticatedRequest) {
|
||||
try {
|
||||
AuthenticationService.enforceAccess((AuthenticatedRequest) request);
|
||||
} catch (TimelyException e) {
|
||||
if (!security.isAllowAnonymousHttpAccess()) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
out.clear();
|
||||
throw e;
|
||||
|
||||
@@ -1,27 +1,35 @@
|
||||
package timely.netty.http.auth;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import timely.api.request.auth.BasicAuthLoginRequest;
|
||||
import timely.auth.AuthenticationService;
|
||||
import timely.auth.TimelyPrincipal;
|
||||
import timely.configuration.Http;
|
||||
import timely.configuration.Security;
|
||||
|
||||
public class BasicAuthLoginRequestHandler extends TimelyLoginRequestHandler<BasicAuthLoginRequest> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BasicAuthLoginRequestHandler.class);
|
||||
|
||||
public BasicAuthLoginRequestHandler(Security security, Http http) {
|
||||
super(security, http);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Authentication authenticate(ChannelHandlerContext ctx, BasicAuthLoginRequest msg)
|
||||
protected TimelyPrincipal authenticate(ChannelHandlerContext ctx, BasicAuthLoginRequest msg, String sessionId)
|
||||
throws AuthenticationException {
|
||||
// Perform the login process using username/password
|
||||
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(msg.getUsername(),
|
||||
msg.getPassword());
|
||||
return AuthenticationService.getAuthenticationManager().authenticate(token);
|
||||
TimelyPrincipal principal = AuthenticationService.authenticate(token, sessionId);
|
||||
|
||||
LOG.debug("Authenticated user {} with authorizations {}", principal.getPrimaryUser().getDn().subjectDN(),
|
||||
principal.getAuthorizationsString());
|
||||
return principal;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package timely.netty.http.auth;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
import timely.auth.SubjectIssuerDNPair;
|
||||
import timely.auth.TimelyPrincipal;
|
||||
import timely.auth.TimelyUser;
|
||||
import timely.auth.util.DnUtils;
|
||||
import timely.auth.util.HttpHeaderUtils;
|
||||
|
||||
public class TimelyAuthenticationToken extends PreAuthenticatedAuthenticationToken {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TimelyAuthenticationToken.class);
|
||||
public static final String PROXIED_ENTITIES_HEADER = "X-ProxiedEntitiesChain";
|
||||
public static final String PROXIED_ISSUERS_HEADER = "X-ProxiedIssuersChain";
|
||||
|
||||
private Multimap<String, String> httpHeaders = null;
|
||||
private X509Certificate clientCert = null;
|
||||
private TimelyPrincipal timelyPrincipal;
|
||||
|
||||
public TimelyAuthenticationToken(String subjectDn, Object clientCert, Multimap<String, String> httpHeaders) {
|
||||
super(subjectDn, clientCert);
|
||||
if (httpHeaders == null) {
|
||||
this.httpHeaders = HashMultimap.create();
|
||||
} else {
|
||||
this.httpHeaders = httpHeaders;
|
||||
}
|
||||
if (clientCert instanceof X509Certificate) {
|
||||
this.clientCert = (X509Certificate) clientCert;
|
||||
}
|
||||
String issuerDn = clientCert == null ? null : this.clientCert.getIssuerDN().getName();
|
||||
String proxiedEntities;
|
||||
String proxiedIssuers;
|
||||
try {
|
||||
proxiedEntities = HttpHeaderUtils.getSingleHeader(httpHeaders, PROXIED_ENTITIES_HEADER, true);
|
||||
proxiedIssuers = HttpHeaderUtils.getSingleHeader(httpHeaders, PROXIED_ISSUERS_HEADER, true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
if (proxiedEntities != null && proxiedIssuers == null) {
|
||||
LOG.error(PROXIED_ENTITIES_HEADER + " supplied, but missing " + PROXIED_ISSUERS_HEADER);
|
||||
throw new IllegalArgumentException(
|
||||
PROXIED_ENTITIES_HEADER + " supplied, but missing " + PROXIED_ISSUERS_HEADER);
|
||||
}
|
||||
List<SubjectIssuerDNPair> entities = extractEntities(subjectDn, issuerDn, proxiedEntities, proxiedIssuers);
|
||||
long now = System.currentTimeMillis();
|
||||
List<TimelyUser> timelyUsers = new ArrayList<>();
|
||||
for (SubjectIssuerDNPair ent : entities) {
|
||||
TimelyUser.UserType userType = DnUtils.isServerDN(ent.subjectDN()) ? TimelyUser.UserType.SERVER
|
||||
: TimelyUser.UserType.USER;
|
||||
timelyUsers.add(new TimelyUser(ent, userType, null, null, null, now));
|
||||
}
|
||||
timelyPrincipal = new TimelyPrincipal(timelyUsers);
|
||||
LOG.trace("Created TimelyAuthenticationToken: {}", timelyPrincipal.getName());
|
||||
}
|
||||
|
||||
public TimelyAuthenticationToken(String subjectDn, String issuerDn, Object clientCert) {
|
||||
super(subjectDn, clientCert);
|
||||
if (clientCert instanceof X509Certificate) {
|
||||
this.clientCert = (X509Certificate) clientCert;
|
||||
}
|
||||
TimelyUser.UserType userType = DnUtils.isServerDN(subjectDn) ? TimelyUser.UserType.SERVER
|
||||
: TimelyUser.UserType.USER;
|
||||
TimelyUser timelyUser = new TimelyUser(SubjectIssuerDNPair.of(subjectDn, issuerDn), userType, null, null, null,
|
||||
System.currentTimeMillis());
|
||||
timelyPrincipal = new TimelyPrincipal(timelyUser);
|
||||
LOG.trace("Created TimelyAuthenticationToken: {}", timelyPrincipal.getName());
|
||||
}
|
||||
|
||||
private List<SubjectIssuerDNPair> extractEntities(String subjectDn, String issuerDn, String proxiedEntities,
|
||||
String proxiedIssuers) {
|
||||
List<SubjectIssuerDNPair> entities = new ArrayList<>();
|
||||
entities.add(SubjectIssuerDNPair.of(subjectDn, issuerDn));
|
||||
if (proxiedEntities != null) {
|
||||
String[] subjects = DnUtils.splitProxiedDNs(proxiedEntities, true);
|
||||
if (proxiedIssuers == null) {
|
||||
throw new IllegalArgumentException("Proxied issuers must be supplied if proxied subjects are supplied");
|
||||
}
|
||||
String[] issuers = DnUtils.splitProxiedDNs(proxiedIssuers, true);
|
||||
if (subjects.length != issuers.length) {
|
||||
throw new IllegalArgumentException("Proxied subjects and issuers don't match up. Subjects="
|
||||
+ proxiedEntities + " , Issuers=" + proxiedIssuers);
|
||||
}
|
||||
|
||||
for (int i = 0; i < subjects.length; ++i) {
|
||||
entities.add(SubjectIssuerDNPair.of(subjects[i], issuers[i]));
|
||||
}
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
|
||||
public Multimap<String, String> getHttpHeaders() {
|
||||
return httpHeaders;
|
||||
}
|
||||
|
||||
public void setTimelyPrincipal(TimelyPrincipal timelyPrincipal) {
|
||||
this.timelyPrincipal = timelyPrincipal;
|
||||
}
|
||||
|
||||
public TimelyPrincipal getTimelyPrincipal() {
|
||||
return timelyPrincipal;
|
||||
}
|
||||
|
||||
public X509Certificate getClientCert() {
|
||||
return clientCert;
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,7 @@ import io.netty.handler.codec.http.cookie.DefaultCookie;
|
||||
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import timely.auth.AuthCache;
|
||||
import timely.auth.TimelyPrincipal;
|
||||
import timely.configuration.Http;
|
||||
import timely.configuration.Security;
|
||||
import timely.netty.Constants;
|
||||
@@ -38,14 +37,15 @@ public abstract class TimelyLoginRequestHandler<T> extends SimpleChannelInboundH
|
||||
protected final void channelRead0(ChannelHandlerContext ctx, T loginRequest) throws Exception {
|
||||
try {
|
||||
LOG.trace("Authenticating {}", loginRequest);
|
||||
Authentication auth = authenticate(ctx, loginRequest);
|
||||
LOG.trace("Authenticated {}", auth);
|
||||
String sessionId = URLEncoder.encode(UUID.randomUUID().toString(), StandardCharsets.UTF_8.name());
|
||||
AuthCache.getCache().put(sessionId, auth);
|
||||
String sessionId = UUID.randomUUID().toString();
|
||||
TimelyPrincipal principal = authenticate(ctx, loginRequest, sessionId);
|
||||
LOG.trace("Authenticated new sessionId {} for user {}", sessionId,
|
||||
principal.getPrimaryUser().getDn().subjectDN());
|
||||
String sessionIdEncoded = URLEncoder.encode(sessionId, StandardCharsets.UTF_8.name());
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
|
||||
response.headers().set(Names.CONTENT_TYPE, Constants.JSON_TYPE);
|
||||
response.headers().set(Names.CONTENT_LENGTH, response.content().readableBytes());
|
||||
DefaultCookie cookie = new DefaultCookie(Constants.COOKIE_NAME, sessionId);
|
||||
DefaultCookie cookie = new DefaultCookie(Constants.COOKIE_NAME, sessionIdEncoded);
|
||||
cookie.setDomain(domain);
|
||||
cookie.setMaxAge(maxAge);
|
||||
cookie.setPath("/");
|
||||
@@ -64,5 +64,6 @@ public abstract class TimelyLoginRequestHandler<T> extends SimpleChannelInboundH
|
||||
|
||||
}
|
||||
|
||||
protected abstract Authentication authenticate(ChannelHandlerContext ctx, T loginRequest) throws Exception;
|
||||
protected abstract TimelyPrincipal authenticate(ChannelHandlerContext ctx, T loginRequest, String entity)
|
||||
throws Exception;
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
package timely.netty.http.auth;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
|
||||
public class TimelyPreAuthenticatedAuthenticationToken extends PreAuthenticatedAuthenticationToken {
|
||||
|
||||
private HttpHeaders httpHeaders = null;
|
||||
|
||||
public TimelyPreAuthenticatedAuthenticationToken(String subjectDN, X509Certificate clientCert,
|
||||
HttpHeaders httpHeaders) {
|
||||
super(subjectDN, clientCert);
|
||||
this.httpHeaders = httpHeaders;
|
||||
}
|
||||
|
||||
public HttpHeaders getHttpHeaders() {
|
||||
return httpHeaders;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package timely.netty.http.auth;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
public interface TimelyUserDetails extends UserDetails {
|
||||
|
||||
Collection<String> getRoles();
|
||||
}
|
||||
@@ -3,35 +3,50 @@ package timely.netty.http.auth;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.api.request.auth.X509LoginRequest;
|
||||
import timely.api.response.TimelyException;
|
||||
import timely.auth.AuthenticationService;
|
||||
import timely.auth.SubjectIssuerDNPair;
|
||||
import timely.auth.TimelyPrincipal;
|
||||
import timely.configuration.Http;
|
||||
import timely.configuration.Security;
|
||||
|
||||
public class X509LoginRequestHandler extends TimelyLoginRequestHandler<X509LoginRequest> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(X509LoginRequestHandler.class);
|
||||
|
||||
public X509LoginRequestHandler(Security security, Http http) {
|
||||
super(security, http);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Authentication authenticate(ChannelHandlerContext ctx, X509LoginRequest loginRequest) throws Exception {
|
||||
protected TimelyPrincipal authenticate(ChannelHandlerContext ctx, X509LoginRequest loginRequest, String sessionId)
|
||||
throws Exception {
|
||||
// If we are operating in 2 way SSL, then get the subjectDN from the
|
||||
// client certificate and perform the login process.
|
||||
SslHandler sslHandler = (SslHandler) ctx.channel().pipeline().get("ssl");
|
||||
if (null != sslHandler) {
|
||||
X509Certificate clientCert = (X509Certificate) sslHandler.engine().getSession().getPeerCertificates()[0];
|
||||
String subjectDN = AuthenticationService.extractDN(clientCert);
|
||||
HttpHeaders httpHeaders = loginRequest.getHttpRequest().headers();
|
||||
TimelyPreAuthenticatedAuthenticationToken token = new TimelyPreAuthenticatedAuthenticationToken(subjectDN,
|
||||
clientCert, httpHeaders);
|
||||
return AuthenticationService.getAuthenticationManager().authenticate(token);
|
||||
} else {
|
||||
throw new IllegalStateException("The expected SSL handler is not in the pipeline.");
|
||||
TimelyPrincipal principal = null;
|
||||
try {
|
||||
X509Certificate clientCert = AuthenticationService.getClientCertificate(ctx);
|
||||
if (clientCert == null) {
|
||||
throw new IllegalArgumentException("No client certificate found");
|
||||
} else {
|
||||
// login endpoint is for direct certificate only, not for proxying
|
||||
TimelyAuthenticationToken token = AuthenticationService.getAuthenticationToken(clientCert,
|
||||
clientCert.getSubjectDN().getName(), clientCert.getIssuerDN().getName());
|
||||
SubjectIssuerDNPair pair = SubjectIssuerDNPair.of(clientCert.getSubjectDN().getName(),
|
||||
clientCert.getIssuerDN().getName());
|
||||
principal = AuthenticationService.authenticate(token, pair, sessionId);
|
||||
LOG.debug("Authenticated user {} with authorizations {}",
|
||||
principal.getPrimaryUser().getDn().subjectDN(), principal.getAuthorizationsString());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "Error performing login", e.getMessage(),
|
||||
e);
|
||||
}
|
||||
return principal;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public class HttpMetricsRequestHandler extends SimpleChannelInboundHandler<Metri
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, MetricsRequest msg) throws Exception {
|
||||
MetricsResponse r = new MetricsResponse(conf);
|
||||
String acceptHeader = msg.getRequestHeaders().get(Names.ACCEPT);
|
||||
String acceptHeader = msg.getRequestHeader(Names.ACCEPT);
|
||||
sendResponse(ctx, r.toHttpResponse(acceptHeader));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package timely.netty.websocket;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageCodec;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.auth.AuthenticationService;
|
||||
import timely.auth.util.HttpHeaderUtils;
|
||||
import timely.netty.http.HttpRequestDecoder;
|
||||
import timely.subscription.SubscriptionRegistry;
|
||||
|
||||
public class WebSocketFullRequestHandler extends MessageToMessageCodec<FullHttpRequest, FullHttpRequest> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WebSocketFullRequestHandler.class);
|
||||
|
||||
public WebSocketFullRequestHandler() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, FullHttpRequest msg, List<Object> out) throws Exception {
|
||||
msg.retain();
|
||||
out.add(msg);
|
||||
// If the session cookie exists, set its value on the ctx.
|
||||
final String sessionId = HttpRequestDecoder.getSessionId(msg);
|
||||
if (sessionId != null) {
|
||||
ctx.channel().attr(SubscriptionRegistry.SESSION_ID_ATTR).set(sessionId);
|
||||
LOG.trace("Found sessionId in WebSocket channel, setting sessionId {} on context", sessionId);
|
||||
}
|
||||
if (msg.headers() != null) {
|
||||
ctx.channel().attr(WebSocketRequestDecoder.HTTP_HEADERS_ATTR)
|
||||
.set(HttpHeaderUtils.toMultimap(msg.headers()));
|
||||
}
|
||||
|
||||
X509Certificate clientCert = AuthenticationService.getClientCertificate(ctx);
|
||||
if (clientCert != null) {
|
||||
ctx.channel().attr(WebSocketRequestDecoder.CLIENT_CERT_ATTR).set(clientCert);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, FullHttpRequest msg, List<Object> out) throws Exception {
|
||||
msg.retain();
|
||||
out.add(msg);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package timely.netty.websocket;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageCodec;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.configuration.Security;
|
||||
import timely.netty.http.HttpRequestDecoder;
|
||||
import timely.subscription.SubscriptionRegistry;
|
||||
|
||||
public class WebSocketHttpCookieHandler extends MessageToMessageCodec<FullHttpRequest, FullHttpRequest> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WebSocketHttpCookieHandler.class);
|
||||
private boolean anonymousAccessAllowed;
|
||||
|
||||
public WebSocketHttpCookieHandler(Security security) {
|
||||
super();
|
||||
this.anonymousAccessAllowed = security.isAllowAnonymousAccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, FullHttpRequest msg, List<Object> out) throws Exception {
|
||||
msg.retain();
|
||||
out.add(msg);
|
||||
// If the session cookie exists, set its value on the ctx.
|
||||
final String sessionId = HttpRequestDecoder.getSessionId(msg, this.anonymousAccessAllowed);
|
||||
ctx.channel().attr(SubscriptionRegistry.SESSION_ID_ATTR).set(sessionId);
|
||||
LOG.info("Found session id in WebSocket channel, setting sessionId {} on context", sessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, FullHttpRequest msg, List<Object> out) throws Exception {
|
||||
msg.retain();
|
||||
out.add(msg);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +1,19 @@
|
||||
package timely.netty.websocket;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
|
||||
import io.netty.handler.ssl.SslCompletionEvent;
|
||||
import io.netty.handler.timeout.IdleState;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import io.netty.util.AttributeKey;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -17,12 +21,19 @@ import timely.api.request.AuthenticatedRequest;
|
||||
import timely.api.request.WebSocketRequest;
|
||||
import timely.api.response.TimelyException;
|
||||
import timely.auth.AuthCache;
|
||||
import timely.auth.AuthenticationService;
|
||||
import timely.auth.SubjectIssuerDNPair;
|
||||
import timely.auth.TimelyPrincipal;
|
||||
import timely.configuration.Security;
|
||||
import timely.netty.http.auth.TimelyAuthenticationToken;
|
||||
import timely.subscription.SubscriptionRegistry;
|
||||
import timely.util.JsonUtil;
|
||||
|
||||
public class WebSocketRequestDecoder extends MessageToMessageDecoder<WebSocketFrame> {
|
||||
|
||||
public static final AttributeKey<Multimap<String, String>> HTTP_HEADERS_ATTR = AttributeKey.newInstance("headers");
|
||||
public static final AttributeKey<X509Certificate> CLIENT_CERT_ATTR = AttributeKey.newInstance("clientCert");
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WebSocketRequestDecoder.class);
|
||||
|
||||
private Security security;
|
||||
@@ -39,14 +50,32 @@ public class WebSocketRequestDecoder extends MessageToMessageDecoder<WebSocketFr
|
||||
String content = frame.text();
|
||||
WebSocketRequest request = JsonUtil.getObjectMapper().readValue(content, WebSocketRequest.class);
|
||||
LOG.trace("Received WS request {}", content);
|
||||
|
||||
String sessionId = ctx.channel().attr(SubscriptionRegistry.SESSION_ID_ATTR).get();
|
||||
if (request instanceof AuthenticatedRequest && !StringUtils.isEmpty(sessionId)) {
|
||||
LOG.info("Found session id in WebSocket channel, setting sessionId {} on request", sessionId);
|
||||
AuthenticatedRequest ar = (AuthenticatedRequest) request;
|
||||
ar.setSessionId(sessionId);
|
||||
Multimap<String, String> headers = ctx.channel().attr(HTTP_HEADERS_ATTR).get();
|
||||
X509Certificate clientCert = ctx.channel().attr(CLIENT_CERT_ATTR).get();
|
||||
if (request instanceof AuthenticatedRequest) {
|
||||
if (headers != null) {
|
||||
((AuthenticatedRequest) request).addHeaders(headers);
|
||||
}
|
||||
TimelyAuthenticationToken token;
|
||||
if (StringUtils.isNotBlank(sessionId)) {
|
||||
TimelyPrincipal principal;
|
||||
((AuthenticatedRequest) request).setSessionId(sessionId);
|
||||
if (AuthCache.containsKey(sessionId)) {
|
||||
principal = AuthCache.get(sessionId);
|
||||
} else {
|
||||
principal = TimelyPrincipal.anonymousPrincipal();
|
||||
}
|
||||
SubjectIssuerDNPair dn = principal.getPrimaryUser().getDn();
|
||||
token = AuthenticationService.getAuthenticationToken(null, dn.subjectDN(), dn.issuerDN());
|
||||
} else if (clientCert != null) {
|
||||
token = AuthenticationService.getAuthenticationToken(clientCert, headers);
|
||||
} else {
|
||||
SubjectIssuerDNPair dn = TimelyPrincipal.anonymousPrincipal().getPrimaryUser().getDn();
|
||||
token = AuthenticationService.getAuthenticationToken(null, dn.subjectDN(), dn.issuerDN());
|
||||
}
|
||||
((AuthenticatedRequest) request).setToken(token);
|
||||
}
|
||||
|
||||
try {
|
||||
request.validate();
|
||||
} catch (IllegalArgumentException e) {
|
||||
@@ -55,8 +84,16 @@ public class WebSocketRequestDecoder extends MessageToMessageDecoder<WebSocketFr
|
||||
return;
|
||||
}
|
||||
try {
|
||||
AuthCache.enforceAccess(security, request);
|
||||
} catch (TimelyException e) {
|
||||
if (request instanceof AuthenticatedRequest) {
|
||||
try {
|
||||
AuthenticationService.enforceAccess((AuthenticatedRequest) request);
|
||||
} catch (TimelyException e) {
|
||||
if (!security.isAllowAnonymousWsAccess()) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
out.clear();
|
||||
LOG.error("Error during access enforcment: " + e.getMessage());
|
||||
ctx.writeAndFlush(new CloseWebSocketFrame(1008, e.getMessage()));
|
||||
@@ -99,10 +136,11 @@ public class WebSocketRequestDecoder extends MessageToMessageDecoder<WebSocketFr
|
||||
}
|
||||
}
|
||||
} else if (evt instanceof SslCompletionEvent) {
|
||||
SslCompletionEvent ssl = (SslCompletionEvent) evt;
|
||||
if (!ssl.isSuccess()) {
|
||||
LOG.error("SSL error: {}", ssl.getClass().getSimpleName(), ssl.cause());
|
||||
}
|
||||
LOG.debug("{}", ((SslCompletionEvent) evt).getClass().getSimpleName());
|
||||
} else if (evt instanceof WebSocketServerProtocolHandler.ServerHandshakeStateEvent) {
|
||||
// The handshake completed succesfully and the channel was upgraded to
|
||||
// websockets
|
||||
LOG.trace("SSL handshake completed successfully, upgraded channel to websockets");
|
||||
} else {
|
||||
LOG.warn("Received unhandled user event {}", evt);
|
||||
}
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ public class WSAddSubscriptionRequestHandler extends SimpleChannelInboundHandler
|
||||
if (add.getDelayTime().isPresent()) {
|
||||
delayTime = add.getDelayTime().get();
|
||||
}
|
||||
s.addMetric(metric, tags, startTime, endTime, delayTime);
|
||||
s.addMetric(add, metric, tags, startTime, endTime, delayTime);
|
||||
} else {
|
||||
LOG.error("Unknown subscription id, create subscription first");
|
||||
ctx.writeAndFlush(new CloseWebSocketFrame(1003, "Unknown subscription id, create subscription first"));
|
||||
|
||||
+7
-4
@@ -9,6 +9,7 @@ import org.slf4j.LoggerFactory;
|
||||
import timely.api.request.subscription.CreateSubscription;
|
||||
import timely.configuration.Configuration;
|
||||
import timely.store.DataStore;
|
||||
import timely.store.cache.DataStoreCache;
|
||||
import timely.subscription.Subscription;
|
||||
import timely.subscription.SubscriptionRegistry;
|
||||
|
||||
@@ -16,10 +17,12 @@ public class WSCreateSubscriptionRequestHandler extends SimpleChannelInboundHand
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WSCreateSubscriptionRequestHandler.class);
|
||||
private final DataStore store;
|
||||
private final DataStoreCache cache;
|
||||
private final Configuration conf;
|
||||
|
||||
public WSCreateSubscriptionRequestHandler(DataStore store, Configuration conf) {
|
||||
public WSCreateSubscriptionRequestHandler(DataStore store, DataStoreCache cache, Configuration conf) {
|
||||
this.store = store;
|
||||
this.cache = cache;
|
||||
this.conf = conf;
|
||||
}
|
||||
|
||||
@@ -27,11 +30,11 @@ public class WSCreateSubscriptionRequestHandler extends SimpleChannelInboundHand
|
||||
protected void channelRead0(ChannelHandlerContext ctx, CreateSubscription create) throws Exception {
|
||||
final String subscriptionId = create.getSubscriptionId();
|
||||
SubscriptionRegistry.get().put(subscriptionId,
|
||||
new Subscription(subscriptionId, create.getSessionId(), store, ctx, this.conf));
|
||||
new Subscription(subscriptionId, create.getSessionId(), store, cache, ctx, this.conf));
|
||||
|
||||
// Store the session id as an attribute on the context.
|
||||
ctx.channel().attr(SubscriptionRegistry.SUBSCRIPTION_ID_ATTR).set(subscriptionId);
|
||||
LOG.info("Created subscription {} on channel {}", subscriptionId, ctx);
|
||||
LOG.info("[{}] Created subscription on channel {}", subscriptionId, ctx);
|
||||
|
||||
ctx.channel().closeFuture().addListener(new ChannelFutureListener() {
|
||||
|
||||
@@ -39,7 +42,7 @@ public class WSCreateSubscriptionRequestHandler extends SimpleChannelInboundHand
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
Subscription s = SubscriptionRegistry.get().remove(subscriptionId);
|
||||
if (null != s) {
|
||||
LOG.info("Channel closed, closing subscriptions for subscriptionId: " + subscriptionId);
|
||||
LOG.info("[{}] Channel closed, closing subscriptions", subscriptionId);
|
||||
s.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.Set;
|
||||
import org.apache.accumulo.core.client.Scanner;
|
||||
import org.apache.accumulo.core.client.TableNotFoundException;
|
||||
import org.apache.accumulo.core.data.Range;
|
||||
import timely.api.request.AuthenticatedRequest;
|
||||
import timely.api.request.timeseries.QueryRequest;
|
||||
import timely.api.request.timeseries.SearchLookupRequest;
|
||||
import timely.api.request.timeseries.SuggestRequest;
|
||||
@@ -36,8 +37,8 @@ public interface DataStore {
|
||||
|
||||
Set<Tag> getColumnFamilies(String metric, Map<String, String> tags) throws TableNotFoundException;
|
||||
|
||||
Scanner createScannerForMetric(String sessionId, String metric, Map<String, String> tags, long startTime,
|
||||
long endTime, int lag, int scannerBatchSize, int scannerReadAhead) throws TimelyException;
|
||||
Scanner createScannerForMetric(AuthenticatedRequest request, String metric, Map<String, String> tags,
|
||||
int scannerBatchSize, int scannerReadAhead) throws TimelyException;
|
||||
|
||||
void setCache(DataStoreCache cache);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package timely.store;
|
||||
|
||||
import static org.apache.accumulo.core.conf.AccumuloConfiguration.getMemoryInBytes;
|
||||
import static org.apache.accumulo.core.conf.AccumuloConfiguration.getTimeInMillis;
|
||||
import static timely.adapter.accumulo.MetricAdapter.VISIBILITY_TAG;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
@@ -18,6 +19,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.Timer;
|
||||
@@ -50,6 +52,8 @@ import org.apache.accumulo.core.data.*;
|
||||
import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
|
||||
import org.apache.accumulo.core.iterators.user.RegExFilter;
|
||||
import org.apache.accumulo.core.security.Authorizations;
|
||||
import org.apache.accumulo.core.security.ColumnVisibility;
|
||||
import org.apache.accumulo.core.security.VisibilityEvaluator;
|
||||
import org.apache.accumulo.core.util.Pair;
|
||||
import org.apache.commons.configuration.BaseConfiguration;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -71,8 +75,10 @@ import timely.api.response.timeseries.SearchLookupResponse;
|
||||
import timely.api.response.timeseries.SearchLookupResponse.Result;
|
||||
import timely.api.response.timeseries.SuggestResponse;
|
||||
import timely.auth.AuthCache;
|
||||
import timely.auth.util.ScannerHelper;
|
||||
import timely.configuration.Accumulo;
|
||||
import timely.configuration.Configuration;
|
||||
import timely.configuration.Security;
|
||||
import timely.model.Metric;
|
||||
import timely.model.Tag;
|
||||
import timely.sample.Aggregation;
|
||||
@@ -118,17 +124,18 @@ public class DataStoreImpl implements DataStore {
|
||||
private final String metricsTable;
|
||||
private final String metaTable;
|
||||
private final InternalMetrics internalMetrics;
|
||||
private final Timer internalMetricsTimer = new Timer("InternalMatricsTimer", true);
|
||||
private final Timer internalMetricsTimer = new Timer("InternalMetricsTimer", true);
|
||||
private final int scannerThreads;
|
||||
private final long maxDownsampleMemory;
|
||||
private final BatchWriterConfig bwConfig;
|
||||
private final List<BatchWriter> writers = new ArrayList<>();
|
||||
private final ThreadLocal<BatchWriter> metaWriter = new ThreadLocal<>();
|
||||
private final ThreadLocal<BatchWriter> batchWriter = new ThreadLocal<>();
|
||||
private boolean anonAccessAllowed = false;
|
||||
private final Security security;
|
||||
private final Map<String, String> ageOff;
|
||||
private final long defaultAgeOffMilliSec;
|
||||
private DataStoreCache cache = null;
|
||||
private final String defaultVisibility;
|
||||
|
||||
public DataStoreImpl(Configuration conf, int numWriteThreads) throws TimelyException {
|
||||
|
||||
@@ -147,7 +154,32 @@ public class DataStoreImpl implements DataStore {
|
||||
bwConfig.setMaxWriteThreads(accumuloConf.getWrite().getThreads());
|
||||
scannerThreads = accumuloConf.getScan().getThreads();
|
||||
maxDownsampleMemory = accumuloConf.getScan().getMaxDownsampleMemory();
|
||||
anonAccessAllowed = conf.getSecurity().isAllowAnonymousAccess();
|
||||
security = conf.getSecurity();
|
||||
defaultVisibility = conf.getDefaultVisibility();
|
||||
if (StringUtils.isNotBlank(defaultVisibility)) {
|
||||
ColumnVisibility vis;
|
||||
try {
|
||||
// validate visibility
|
||||
vis = new ColumnVisibility(defaultVisibility);
|
||||
LOG.info("Using defaultVisibility: {}", vis.toString());
|
||||
} catch (RuntimeException e) {
|
||||
LOG.error("Error validating defaultVisibility " + defaultVisibility + " " + e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
try {
|
||||
String whoami = connector.whoami();
|
||||
Authorizations auths = connector.securityOperations().getUserAuthorizations(whoami);
|
||||
VisibilityEvaluator eval = new VisibilityEvaluator(auths);
|
||||
if (!eval.evaluate(vis)) {
|
||||
throw new IllegalArgumentException("Accumulo user " + whoami + " with authorization "
|
||||
+ auths.toString() + " can not see data with defaultVisibility " + vis.toString());
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
metricsTable = conf.getMetricsTable();
|
||||
if (metricsTable.contains(".")) {
|
||||
@@ -176,7 +208,7 @@ public class DataStoreImpl implements DataStore {
|
||||
}
|
||||
try {
|
||||
this.removeAgeOffIterators(connector, metricsTable);
|
||||
this.applyAgeOffIterator(connector, metricsTable, true);
|
||||
this.applyMetricAgeOffIterator(connector, metricsTable);
|
||||
} catch (Exception e1) {
|
||||
Throwable cause = e1.getCause();
|
||||
if (cause.getMessage().contains("conflict")) {
|
||||
@@ -197,7 +229,7 @@ public class DataStoreImpl implements DataStore {
|
||||
}
|
||||
try {
|
||||
this.removeAgeOffIterators(connector, metaTable);
|
||||
this.applyAgeOffIterator(connector, metaTable, false);
|
||||
this.applyMetaAgeOffIterator(connector, metaTable);
|
||||
} catch (Exception e1) {
|
||||
Throwable cause = e1.getCause();
|
||||
if (cause.getMessage().contains("conflict")) {
|
||||
@@ -250,13 +282,15 @@ public class DataStoreImpl implements DataStore {
|
||||
}
|
||||
}
|
||||
|
||||
private void applyAgeOffIterator(Connector con, String tableName, boolean useIterator) throws Exception {
|
||||
IteratorSetting ageOffIteratorSettings = null;
|
||||
if (useIterator) {
|
||||
ageOffIteratorSettings = new IteratorSetting(100, "ageoff", MetricAgeOffIterator.class, this.ageOff);
|
||||
} else {
|
||||
ageOffIteratorSettings = new IteratorSetting(100, "ageoff", MetricAgeOffFilter.class, this.ageOff);
|
||||
}
|
||||
private void applyMetricAgeOffIterator(Connector con, String tableName) throws Exception {
|
||||
IteratorSetting ageOffIteratorSettings = new IteratorSetting(100, "ageoffmetrics", MetricAgeOffIterator.class,
|
||||
this.ageOff);
|
||||
connector.tableOperations().attachIterator(tableName, ageOffIteratorSettings, AGEOFF_SCOPES);
|
||||
}
|
||||
|
||||
private void applyMetaAgeOffIterator(Connector con, String tableName) throws Exception {
|
||||
IteratorSetting ageOffIteratorSettings = new IteratorSetting(100, "ageoffmeta", MetaAgeOffIterator.class,
|
||||
this.ageOff);
|
||||
connector.tableOperations().attachIterator(tableName, ageOffIteratorSettings, AGEOFF_SCOPES);
|
||||
}
|
||||
|
||||
@@ -278,6 +312,17 @@ public class DataStoreImpl implements DataStore {
|
||||
public void store(Metric metric, boolean cacheEnabled) {
|
||||
LOG.trace("Received Store Request for: {}", metric);
|
||||
|
||||
// if default visibility is configured and current metric does not contain a
|
||||
// visibility
|
||||
// add it here, so that the cache and accumulo stores both have the correct
|
||||
// visibility
|
||||
if (StringUtils.isNotBlank(defaultVisibility)) {
|
||||
Optional<Tag> visTag = metric.getTags().stream().filter(t -> t.getKey().equals(VISIBILITY_TAG)).findFirst();
|
||||
if (!visTag.isPresent()) {
|
||||
metric.addTag(new Tag(VISIBILITY_TAG, defaultVisibility));
|
||||
}
|
||||
}
|
||||
|
||||
if (cache != null && cacheEnabled) {
|
||||
cache.store(metric);
|
||||
}
|
||||
@@ -395,6 +440,7 @@ public class DataStoreImpl implements DataStore {
|
||||
@Override
|
||||
public SuggestResponse suggest(SuggestRequest request) throws TimelyException {
|
||||
SuggestResponse result = new SuggestResponse();
|
||||
Scanner scanner = null;
|
||||
try {
|
||||
if (request.getType().equals("metrics")) {
|
||||
Range range;
|
||||
@@ -414,7 +460,7 @@ public class DataStoreImpl implements DataStore {
|
||||
end.append(lastBytes, 0, lastBytes.length);
|
||||
range = new Range(start, end);
|
||||
}
|
||||
Scanner scanner = connector.createScanner(metaTable, Authorizations.EMPTY);
|
||||
scanner = connector.createScanner(metaTable, Authorizations.EMPTY);
|
||||
scanner.setRange(range);
|
||||
List<String> metrics = new ArrayList<>();
|
||||
for (Entry<Key, Value> metric : scanner) {
|
||||
@@ -429,6 +475,10 @@ public class DataStoreImpl implements DataStore {
|
||||
LOG.error("Error during suggest: " + ex.getMessage(), ex);
|
||||
throw new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(),
|
||||
"Error during suggest: " + ex.getMessage(), ex.getMessage(), ex);
|
||||
} finally {
|
||||
if (scanner != null) {
|
||||
scanner.close();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -463,28 +513,34 @@ public class DataStoreImpl implements DataStore {
|
||||
tagPatterns.put(k, Pattern.compile(v));
|
||||
});
|
||||
try {
|
||||
List<Result> resultField = new ArrayList<>();
|
||||
Scanner scanner = connector.createScanner(metaTable, Authorizations.EMPTY);
|
||||
Key start = new Key(Meta.VALUE_PREFIX + msg.getQuery());
|
||||
Key end = start.followingKey(PartialKey.ROW);
|
||||
Range range = new Range(start, end);
|
||||
scanner.setRange(range);
|
||||
tags.keySet().forEach(k -> scanner.fetchColumnFamily(new Text(k)));
|
||||
int total = 0;
|
||||
for (Entry<Key, Value> entry : scanner) {
|
||||
Meta metaEntry = Meta.parse(entry.getKey(), entry.getValue());
|
||||
if (matches(metaEntry.getTagKey(), metaEntry.getTagValue(), tagPatterns)) {
|
||||
if (resultField.size() < msg.getLimit()) {
|
||||
Result r = new Result();
|
||||
r.putTag(metaEntry.getTagKey(), metaEntry.getTagValue());
|
||||
resultField.add(r);
|
||||
final Scanner scanner = connector.createScanner(metaTable, Authorizations.EMPTY);
|
||||
try {
|
||||
List<Result> resultField = new ArrayList<>();
|
||||
Key start = new Key(Meta.VALUE_PREFIX + msg.getQuery());
|
||||
Key end = start.followingKey(PartialKey.ROW);
|
||||
Range range = new Range(start, end);
|
||||
scanner.setRange(range);
|
||||
tags.keySet().forEach(k -> scanner.fetchColumnFamily(new Text(k)));
|
||||
int total = 0;
|
||||
for (Entry<Key, Value> entry : scanner) {
|
||||
Meta metaEntry = Meta.parse(entry.getKey(), entry.getValue());
|
||||
if (matches(metaEntry.getTagKey(), metaEntry.getTagValue(), tagPatterns)) {
|
||||
if (resultField.size() < msg.getLimit()) {
|
||||
Result r = new Result();
|
||||
r.putTag(metaEntry.getTagKey(), metaEntry.getTagValue());
|
||||
resultField.add(r);
|
||||
}
|
||||
total++;
|
||||
}
|
||||
total++;
|
||||
}
|
||||
result.setResults(resultField);
|
||||
result.setTotalResults(total);
|
||||
result.setTime((int) (System.currentTimeMillis() - startMillis));
|
||||
} finally {
|
||||
if (scanner != null) {
|
||||
scanner.close();
|
||||
}
|
||||
}
|
||||
result.setResults(resultField);
|
||||
result.setTotalResults(total);
|
||||
result.setTime((int) (System.currentTimeMillis() - startMillis));
|
||||
} catch (Exception ex) {
|
||||
LOG.error("Error during lookup: " + ex.getMessage(), ex);
|
||||
throw new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(),
|
||||
@@ -505,8 +561,7 @@ public class DataStoreImpl implements DataStore {
|
||||
@Override
|
||||
public List<QueryResponse> query(QueryRequest msg) throws TimelyException {
|
||||
List<QueryResponse> result = new ArrayList<>();
|
||||
|
||||
LOG.debug("Query request {}", msg);
|
||||
LOG.debug("Query for [{}] [{}]", msg.getUserName(), msg);
|
||||
long requestedStartTs = msg.getStart();
|
||||
long requestedEndTs = msg.getEnd();
|
||||
StringBuilder metricList = new StringBuilder();
|
||||
@@ -549,8 +604,9 @@ public class DataStoreImpl implements DataStore {
|
||||
if (percentServedFromCache > 100) {
|
||||
percentServedFromCache = 100;
|
||||
}
|
||||
LOG.debug("Cache query time:{} duration (min):{} metrics:{} results:{} percentFromCache:{}",
|
||||
(System.currentTimeMillis() - now),
|
||||
LOG.debug(
|
||||
"Cache query for [{}] time:{} duration (min):{} metrics:{} results:{} percentFromCache:{}",
|
||||
msg.getUserName(), (System.currentTimeMillis() - now),
|
||||
((requestedEndTs - requestedStartTs) / (1000 * 60)), metricList.toString(), z,
|
||||
percentServedFromCache);
|
||||
allSeries.putAll(cachedMetrics);
|
||||
@@ -581,7 +637,8 @@ public class DataStoreImpl implements DataStore {
|
||||
if (endOfLastPeriod > startOfFirstPeriod) {
|
||||
BatchScanner scanner = null;
|
||||
try {
|
||||
scanner = connector.createBatchScanner(metricsTable, getSessionAuthorizations(msg),
|
||||
Collection<Authorizations> authorizations = getSessionAuthorizations(msg);
|
||||
scanner = ScannerHelper.createBatchScanner(connector, metricsTable, authorizations,
|
||||
scannerThreads);
|
||||
List<String> tagOrder = prioritizeTags(query.getMetric(), query.getTags());
|
||||
Map<String, String> orderedTags = orderTags(tagOrder, query.getTags());
|
||||
@@ -658,8 +715,9 @@ public class DataStoreImpl implements DataStore {
|
||||
result.add(convertToQueryResponse(query, entry.getKey(), entry.getValue(), tsDivisor));
|
||||
}
|
||||
}
|
||||
LOG.debug("Query time:{} duration:{} metrics:{} results:{}", (System.currentTimeMillis() - now),
|
||||
((requestedEndTs - requestedStartTs) / (1000 * 60)), metricList.toString(), numResults);
|
||||
LOG.debug("Query for [{}] time:{} duration:{} metrics:{} results:{}", msg.getUserName(),
|
||||
(System.currentTimeMillis() - now), ((requestedEndTs - requestedStartTs) / (1000 * 60)),
|
||||
metricList.toString(), numResults);
|
||||
internalMetrics.addQueryResponse(result.size(), (System.currentTimeMillis() - now));
|
||||
return result;
|
||||
} catch (ClassNotFoundException | IOException | TableNotFoundException ex) {
|
||||
@@ -753,70 +811,77 @@ public class DataStoreImpl implements DataStore {
|
||||
|
||||
public Set<Tag> getColumnFamilies(String metric, Map<String, String> requestedTags) throws TableNotFoundException {
|
||||
|
||||
Map<String, String> tags = (requestedTags == null) ? new LinkedHashMap<>() : requestedTags;
|
||||
LOG.trace("Looking for requested tags: {}", tags);
|
||||
Scanner meta = connector.createScanner(metaTable, Authorizations.EMPTY);
|
||||
Text start = new Text(Meta.VALUE_PREFIX + metric);
|
||||
Text end = new Text(Meta.VALUE_PREFIX + metric + "\\x0000");
|
||||
end.append(new byte[] { (byte) 0xff }, 0, 1);
|
||||
meta.setRange(new Range(start, end));
|
||||
// Only look for the meta entries that match our tags, if any
|
||||
boolean onlyFirstRow = false;
|
||||
Entry<String, String> first = null;
|
||||
// Set the columns on the meta scanner based on the first tag
|
||||
// in the set of tags passed in the query. If no tags are present
|
||||
// then we are only going to return the first tag name present in the
|
||||
// meta table.
|
||||
Iterator<Entry<String, String>> tagIter = tags.entrySet().iterator();
|
||||
if (tagIter.hasNext()) {
|
||||
first = tagIter.next();
|
||||
if (isTagValueRegex(first.getValue())) {
|
||||
meta.fetchColumnFamily(new Text(first.getKey()));
|
||||
} else {
|
||||
meta.fetchColumn(new Text(first.getKey()), new Text(first.getValue()));
|
||||
}
|
||||
} else {
|
||||
// grab all of the values found for the first tag for the metric
|
||||
onlyFirstRow = true;
|
||||
}
|
||||
final boolean ONLY_RETURN_FIRST_TAG = onlyFirstRow;
|
||||
Iterator<Entry<Key, Value>> iter = meta.iterator();
|
||||
Iterator<Pair<String, String>> knownKeyValues = new Iterator<Pair<String, String>>() {
|
||||
|
||||
Text firstTag = null;
|
||||
Text tagName = null;
|
||||
Text tagValue = null;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (iter.hasNext()) {
|
||||
Entry<Key, Value> metaEntry = iter.next();
|
||||
if (null == firstTag) {
|
||||
firstTag = metaEntry.getKey().getColumnFamily();
|
||||
}
|
||||
tagName = metaEntry.getKey().getColumnFamily();
|
||||
tagValue = metaEntry.getKey().getColumnQualifier();
|
||||
LOG.trace("Found tag entry {}={}", tagName, tagValue);
|
||||
|
||||
if (ONLY_RETURN_FIRST_TAG && !tagName.equals(firstTag)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
Scanner meta = null;
|
||||
try {
|
||||
Map<String, String> tags = (requestedTags == null) ? new LinkedHashMap<>() : requestedTags;
|
||||
LOG.trace("Looking for requested tags: {}", tags);
|
||||
meta = connector.createScanner(metaTable, Authorizations.EMPTY);
|
||||
Text start = new Text(Meta.VALUE_PREFIX + metric);
|
||||
Text end = new Text(Meta.VALUE_PREFIX + metric + "\\x0000");
|
||||
end.append(new byte[] { (byte) 0xff }, 0, 1);
|
||||
meta.setRange(new Range(start, end));
|
||||
// Only look for the meta entries that match our tags, if any
|
||||
boolean onlyFirstRow = false;
|
||||
Entry<String, String> first = null;
|
||||
// Set the columns on the meta scanner based on the first tag
|
||||
// in the set of tags passed in the query. If no tags are present
|
||||
// then we are only going to return the first tag name present in the
|
||||
// meta table.
|
||||
Iterator<Entry<String, String>> tagIter = tags.entrySet().iterator();
|
||||
if (tagIter.hasNext()) {
|
||||
first = tagIter.next();
|
||||
if (isTagValueRegex(first.getValue())) {
|
||||
meta.fetchColumnFamily(new Text(first.getKey()));
|
||||
} else {
|
||||
meta.fetchColumn(new Text(first.getKey()), new Text(first.getValue()));
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
// grab all of the values found for the first tag for the metric
|
||||
onlyFirstRow = true;
|
||||
}
|
||||
final boolean ONLY_RETURN_FIRST_TAG = onlyFirstRow;
|
||||
Iterator<Entry<Key, Value>> iter = meta.iterator();
|
||||
Iterator<Pair<String, String>> knownKeyValues = new Iterator<Pair<String, String>>() {
|
||||
|
||||
@Override
|
||||
public Pair<String, String> next() {
|
||||
LOG.trace("Returning tag {}={}", tagName, tagValue);
|
||||
return new Pair<>(tagName.toString(), tagValue.toString());
|
||||
Text firstTag = null;
|
||||
Text tagName = null;
|
||||
Text tagValue = null;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (iter.hasNext()) {
|
||||
Entry<Key, Value> metaEntry = iter.next();
|
||||
if (null == firstTag) {
|
||||
firstTag = metaEntry.getKey().getColumnFamily();
|
||||
}
|
||||
tagName = metaEntry.getKey().getColumnFamily();
|
||||
tagValue = metaEntry.getKey().getColumnQualifier();
|
||||
LOG.trace("Found tag entry {}={}", tagName, tagValue);
|
||||
|
||||
if (ONLY_RETURN_FIRST_TAG && !tagName.equals(firstTag)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<String, String> next() {
|
||||
LOG.trace("Returning tag {}={}", tagName, tagValue);
|
||||
return new Pair<>(tagName.toString(), tagValue.toString());
|
||||
}
|
||||
};
|
||||
// Expand the list of tags in the meta table for this metric that
|
||||
// matches
|
||||
// the pattern of the first tag in the query. The resulting set of tags
|
||||
// will be used to fetch specific columns from the metric table.
|
||||
return expandTagValues(first, knownKeyValues);
|
||||
} finally {
|
||||
if (meta != null) {
|
||||
meta.close();
|
||||
}
|
||||
};
|
||||
// Expand the list of tags in the meta table for this metric that
|
||||
// matches
|
||||
// the pattern of the first tag in the query. The resulting set of tags
|
||||
// will be used to fetch specific columns from the metric table.
|
||||
return expandTagValues(first, knownKeyValues);
|
||||
}
|
||||
}
|
||||
|
||||
private void setQueryColumns(ScannerBase scanner, String metric, Map<String, String> tags, Set<Tag> colFamValues)
|
||||
@@ -945,32 +1010,8 @@ public class DataStoreImpl implements DataStore {
|
||||
return Aggregator.getAggregator(query.getAggregator());
|
||||
}
|
||||
|
||||
private Authorizations getSessionAuthorizations(AuthenticatedRequest request) {
|
||||
return getSessionAuthorizations(request.getSessionId());
|
||||
}
|
||||
|
||||
private Authorizations getSessionAuthorizations(String sessionId) {
|
||||
if (anonAccessAllowed) {
|
||||
if (StringUtils.isEmpty(sessionId)) {
|
||||
return Authorizations.EMPTY;
|
||||
} else {
|
||||
Authorizations auths = AuthCache.getAuthorizations(sessionId);
|
||||
if (null == auths) {
|
||||
auths = Authorizations.EMPTY;
|
||||
}
|
||||
return auths;
|
||||
}
|
||||
} else {
|
||||
if (StringUtils.isEmpty(sessionId)) {
|
||||
throw new IllegalArgumentException("session id cannot be null");
|
||||
} else {
|
||||
Authorizations auths = AuthCache.getAuthorizations(sessionId);
|
||||
if (null == auths) {
|
||||
throw new IllegalStateException("No auths found for sessionId: " + sessionId);
|
||||
}
|
||||
return auths;
|
||||
}
|
||||
}
|
||||
private Collection<Authorizations> getSessionAuthorizations(AuthenticatedRequest request) {
|
||||
return AuthCache.getAuthorizations(request, security);
|
||||
}
|
||||
|
||||
public long getAgeOffForMetric(String metricName) {
|
||||
@@ -982,27 +1023,15 @@ public class DataStoreImpl implements DataStore {
|
||||
}
|
||||
}
|
||||
|
||||
public Scanner createScannerForMetric(String sessionId, String metric, Map<String, String> tags, long startTime,
|
||||
long endTime, int lag, int scannerBatchSize, int scannerReadAhead) throws TimelyException {
|
||||
public Scanner createScannerForMetric(AuthenticatedRequest request, String metric, Map<String, String> tags,
|
||||
int scannerBatchSize, int scannerReadAhead) throws TimelyException {
|
||||
try {
|
||||
Authorizations auths = null;
|
||||
try {
|
||||
auths = getSessionAuthorizations(sessionId);
|
||||
} catch (NullPointerException npe) {
|
||||
// Session id being used for metric scanner, but session Id does
|
||||
// not exist in Auth Cache. Use Empty auths if anonymous access
|
||||
// allowed
|
||||
if (anonAccessAllowed) {
|
||||
auths = Authorizations.EMPTY;
|
||||
} else {
|
||||
throw npe;
|
||||
}
|
||||
}
|
||||
LOG.debug("Creating metric scanner for session: {} with auths: {}", sessionId, auths);
|
||||
Scanner s = connector.createScanner(this.metricsTable, auths);
|
||||
if (null == metric) {
|
||||
throw new IllegalArgumentException("metric name must be specified");
|
||||
}
|
||||
Collection<Authorizations> auths = getSessionAuthorizations(request);
|
||||
LOG.debug("Creating metric scanner for [{}] with auths: {}", request.getUserName(), auths);
|
||||
Scanner s = ScannerHelper.createScanner(connector, this.metricsTable, auths);
|
||||
if (tags == null) {
|
||||
tags = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
Loaded 100 of 137 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user