package com.kryptnostic.rhizome.pods;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.net.ssl.SSLContext;
import com.datastax.driver.core.*;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.ssl.SSLContextBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
import com.datastax.driver.core.Cluster.Builder;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.kryptnostic.rhizome.cassandra.CassandraTableBuilder;
import com.kryptnostic.rhizome.cassandra.EmbeddedCassandraManager;
import com.kryptnostic.rhizome.cassandra.TableDef;
import com.kryptnostic.rhizome.configuration.RhizomeConfiguration;
import com.kryptnostic.rhizome.configuration.cassandra.CassandraConfiguration;
import com.kryptnostic.rhizome.configuration.cassandra.CassandraConfigurations;
import com.kryptnostic.rhizome.configuration.cassandra.Clusters;
import com.kryptnostic.rhizome.configuration.cassandra.Sessions;
import com.kryptnostic.rhizome.configuration.cassandra.TableDefSource;
@Configuration
@Profile( CassandraPod.CASSANDRA_PROFILE )
@Import( ConfigurationPod.class )
public class CassandraPod {
public static final String CASSANDRA_PROFILE = "cassandra";
public static final String RANDOM_PORTS_YAML = "cu-cassandra-rndport-workaround.yaml";
public static final String EMBEDDED_YAML = "cu-cassandra.yaml";
public static final String CREATE_KEYSPACE = "CREATE KEYSPACE IF NOT EXISTS %s WITH REPLICATION={ 'class' : 'SimpleStrategy', 'replication_factor' : %d } AND DURABLE_WRITES=true";
private static final Logger logger = LoggerFactory.getLogger( CassandraPod.class );
@Inject
private RhizomeConfiguration configuration;
private static EmbeddedCassandraManager ecm = s -> logger
.warn( "\n****************************************"
+ "\nA request was made to try and start cassandra with {}. No action was taking since you are using the no-op cassandra manager"
+ "\nTry using lambda syntax such as EmbeddedCassandraUnitHelper::startEmbeddedCassandra to setup a cassandra manager in the cassandra pod.",
s );
@Autowired(
required = false )
private Set<TypeCodec<?>> codecs;
@Autowired(
required = false )
private Set<TableDefSource> tables;
private static PoolingOptions getPoolingOptions() {
PoolingOptions poolingOptions = new PoolingOptions();
poolingOptions
.setMaxRequestsPerConnection( HostDistance.LOCAL, 32768 )
.setMaxRequestsPerConnection( HostDistance.REMOTE, 32768 );
return poolingOptions;
}
public static Builder clusterBuilder( CassandraConfiguration cassandraConfiguration ) {
Builder builder = new Cluster.Builder();
builder.withCompression( cassandraConfiguration.getCompression() )
.withSocketOptions( getSocketOptions() )
.withQueryOptions( new QueryOptions().setConsistencyLevel( cassandraConfiguration.getConsistencyLevel() ) )
.withPoolingOptions( getPoolingOptions() )
.withProtocolVersion( ProtocolVersion.V4 )
.addContactPoints( cassandraConfiguration.getCassandraSeedNodes() );
if ( cassandraConfiguration.isSslEnabled() ) {
SSLContext context = null;
try {
context = SSLContextBuilder.create().loadTrustMaterial( new TrustSelfSignedStrategy() ).build();
} catch ( NoSuchAlgorithmException | KeyStoreException | KeyManagementException e ) {
logger.error( "Unable to configure SSL for Cassanda Java Driver" );
}
builder.withSSL( JdkSSLOptions.builder().withSSLContext( context ).build() );
}
return builder;
}
public static SocketOptions getSocketOptions() {
return new SocketOptions().setReadTimeoutMillis( 60000 * 10 );
}
@Bean
public CassandraConfiguration cassandraConfiguration() {
if ( configuration == null || !configuration.getCassandraConfiguration().isPresent() ) {
throw new RuntimeException(
"Cassandra configuration is missing. Please add a cassandra configuration to rhizome.yaml" );
}
CassandraConfiguration cassandraConfiguration = configuration.getCassandraConfiguration().get();
if ( cassandraConfiguration == null ) {
logger.error(
"Seed nodes not found in cassandra configuration. Please add seed nodes to cassandra configuration block in rhizome.yaml" );
} else {
if ( cassandraConfiguration.isEmbedded() ) {
ecm.start( EMBEDDED_YAML );
} else if ( cassandraConfiguration.isEmbedded() && cassandraConfiguration.isRandomPorts() ) {
ecm.start( RANDOM_PORTS_YAML );
}
logger.info( "Using the following seeds for cassandra: {}",
cassandraConfiguration.getCassandraSeedNodes().stream().map( s -> s.getHostAddress() )
.collect( Collectors.toList() ) );
}
return cassandraConfiguration;
}
@Bean
public Session session() {
Session session = cluster().newSession();
CassandraConfiguration cc = cassandraConfiguration();
// ensure the keyspace exists
session.execute( String.format( CREATE_KEYSPACE, cc.getKeyspace(), cc.getReplicationFactor() ) );
// Create all the required tables.
if ( tables != null ) {
logger.info( "Setting up tables and secondary indices." );
tables.stream()
.flatMap( Supplier::get )
.map( TableDef::getBuilder )
.map( CassandraTableBuilder::build )
.flatMap( List::stream )
.peek( query -> logger.info( "Executing query: {}", query ) )
.forEach( session::execute );
}
return session;
}
@Bean
public CodecRegistry codecRegistry() {
// TODO: Explicitly construct default instance codecs so that not all sessions end up having all codecs. P2
CodecRegistry registry = CodecRegistry.DEFAULT_INSTANCE;
if ( codecs != null ) {
registry.register( codecs );
}
return registry;
}
@Bean
public Cluster cluster() {
CassandraConfiguration cc = cassandraConfiguration();
if ( cc.isRandomPorts() || cc.isEmbedded() ) {
return ecm.cluster();
}
return clusterBuilder( cc )
.withCodecRegistry( codecRegistry() )
.build();
}
@Bean
public Clusters clusters() {
return new Clusters( Maps.transformValues( getConfigurations(),
cassandraConfiguration -> clusterBuilder( cassandraConfiguration )
.withCodecRegistry( codecRegistry() )
.build() ) );
}
@Bean
public Sessions sessions() {
return new Sessions( Maps.transformValues( clusters(), cluster -> cluster.newSession() ) );
}
@VisibleForTesting
public void setRhizomeConfig( RhizomeConfiguration config ) {
this.configuration = config;
}
@VisibleForTesting
public void setCodecs( Set<TypeCodec<?>> codecs ) {
this.codecs = codecs;
}
private CassandraConfigurations getConfigurations() {
return configuration.getCassandraConfigurations().or( new CassandraConfigurations() );
}
public static void setEmbeddedCassandraManager( EmbeddedCassandraManager ecManager ) {
ecm = Preconditions.checkNotNull( ecManager, "Cannot set a null cassandra manager" );
}
}