/**
* Copyright 2015-2016 The OpenZipkin Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package zipkin.storage.cassandra3;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.HostDistance;
import com.datastax.driver.core.KeyspaceMetadata;
import com.datastax.driver.core.PoolingOptions;
import com.datastax.driver.core.QueryLogger;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.TypeCodec;
import com.datastax.driver.core.policies.DCAwareRoundRobinPolicy;
import com.datastax.driver.core.policies.LatencyAwarePolicy;
import com.datastax.driver.core.policies.RoundRobinPolicy;
import com.datastax.driver.core.policies.TokenAwarePolicy;
import com.datastax.driver.mapping.MappingManager;
import com.google.common.collect.Sets;
import com.google.common.io.Closer;
import com.google.common.net.HostAndPort;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import zipkin.storage.cassandra3.Schema.AnnotationUDT;
import zipkin.storage.cassandra3.Schema.BinaryAnnotationUDT;
import zipkin.storage.cassandra3.Schema.EndpointUDT;
import zipkin.storage.cassandra3.Schema.TraceIdUDT;
import zipkin.storage.cassandra3.Schema.TypeCodecImpl;
import static zipkin.storage.cassandra3.Schema.DEFAULT_KEYSPACE;
/**
* Creates a session and ensures schema if configured. Closes the cluster and session if any
* exception occurred.
*/
final class DefaultSessionFactory implements Cassandra3Storage.SessionFactory {
/**
* Creates a session and ensures schema if configured. Closes the cluster and session if any
* exception occurred.
*/
@Override public Session create(Cassandra3Storage cassandra) {
Closer closer = Closer.create();
try {
Cluster cluster = closer.register(buildCluster(cassandra));
cluster.register(new QueryLogger.Builder().build());
Session session;
if (cassandra.ensureSchema) {
session = closer.register(cluster.connect());
Schema.ensureExists(cassandra.keyspace, session);
session.execute("USE " + cassandra.keyspace);
} else {
session = cluster.connect(cassandra.keyspace);
}
initializeUDTs(session);
return session;
} catch (RuntimeException e) {
try {
closer.close();
} catch (IOException ignored) {
}
throw e;
}
}
private static void initializeUDTs(Session session) {
Schema.ensureExists(DEFAULT_KEYSPACE + "_udts", session);
MappingManager mapping = new MappingManager(session);
// The UDTs are hardcoded against the zipkin keyspace.
// If a different keyspace is being used the codecs must be re-applied to this different keyspace
TypeCodec<TraceIdUDT> traceIdCodec = mapping.udtCodec(TraceIdUDT.class);
TypeCodec<EndpointUDT> endpointCodec = mapping.udtCodec(EndpointUDT.class);
TypeCodec<AnnotationUDT> annoCodec = mapping.udtCodec(AnnotationUDT.class);
TypeCodec<BinaryAnnotationUDT> bAnnoCodec = mapping.udtCodec(BinaryAnnotationUDT.class);
KeyspaceMetadata keyspace =
session.getCluster().getMetadata().getKeyspace(session.getLoggedKeyspace());
session.getCluster().getConfiguration().getCodecRegistry()
.register(
new TypeCodecImpl(keyspace.getUserType("trace_id"), TraceIdUDT.class, traceIdCodec))
.register(
new TypeCodecImpl(keyspace.getUserType("endpoint"), EndpointUDT.class, endpointCodec))
.register(
new TypeCodecImpl(keyspace.getUserType("annotation"), AnnotationUDT.class, annoCodec))
.register(
new TypeCodecImpl(keyspace.getUserType("binary_annotation"), BinaryAnnotationUDT.class,
bAnnoCodec));
}
// Visible for testing
static Cluster buildCluster(Cassandra3Storage cassandra) {
Cluster.Builder builder = Cluster.builder();
List<InetSocketAddress> contactPoints = parseContactPoints(cassandra);
int defaultPort = findConnectPort(contactPoints);
builder.addContactPointsWithPorts(contactPoints);
builder.withPort(defaultPort); // This ends up protocolOptions.port
if (cassandra.username != null && cassandra.password != null) {
builder.withCredentials(cassandra.username, cassandra.password);
}
builder.withRetryPolicy(ZipkinRetryPolicy.INSTANCE);
builder.withLoadBalancingPolicy(new TokenAwarePolicy(new LatencyAwarePolicy.Builder(
cassandra.localDc != null
? DCAwareRoundRobinPolicy.builder().withLocalDc(cassandra.localDc).build()
: new RoundRobinPolicy()
// This can select remote, but LatencyAwarePolicy will prefer local
).build()));
builder.withPoolingOptions(new PoolingOptions().setMaxConnectionsPerHost(
HostDistance.LOCAL, cassandra.maxConnections
));
if (cassandra.useSsl) {
builder = builder.withSSL();
}
return builder.build();
}
static List<InetSocketAddress> parseContactPoints(Cassandra3Storage cassandra) {
List<InetSocketAddress> result = new LinkedList<>();
for (String contactPoint : cassandra.contactPoints.split(",")) {
HostAndPort parsed = HostAndPort.fromString(contactPoint);
result.add(
new InetSocketAddress(parsed.getHostText(), parsed.getPortOrDefault(9042)));
}
return result;
}
/** Returns the consistent port across all contact points or 9042 */
static int findConnectPort(List<InetSocketAddress> contactPoints) {
Set<Integer> ports = Sets.newLinkedHashSet();
for (InetSocketAddress contactPoint : contactPoints) {
ports.add(contactPoint.getPort());
}
return ports.size() == 1 ? ports.iterator().next() : 9042;
}
}