/** * Copyright 2015-2017 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.DataType; import com.datastax.driver.core.KeyspaceMetadata; import com.datastax.driver.core.ProtocolVersion; import com.datastax.driver.core.Session; import com.datastax.driver.core.TypeCodec; import com.datastax.driver.core.exceptions.InvalidTypeException; import com.datastax.driver.mapping.annotations.UDT; import com.google.common.io.CharStreams; import com.google.common.net.InetAddresses; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import zipkin.Annotation; import zipkin.BinaryAnnotation; import zipkin.Endpoint; import static zipkin.internal.Util.UTF_8; import static zipkin.internal.Util.toLowerHex; final class Schema { private static final Logger LOG = LoggerFactory.getLogger(Schema.class); static final String TABLE_TRACES = "traces"; static final String TABLE_TRACE_BY_SERVICE_SPAN = "trace_by_service_span"; static final String TABLE_SERVICE_SPANS = "span_name_by_service"; static final String TABLE_DEPENDENCIES = "dependencies"; static final String DEFAULT_KEYSPACE = "zipkin3"; private static final String SCHEMA_RESOURCE = "/cassandra3-schema.cql"; private Schema() { } static Metadata readMetadata(Session session) { KeyspaceMetadata keyspaceMetadata = getKeyspaceMetadata(session); Map<String, String> replication = keyspaceMetadata.getReplication(); if ("SimpleStrategy".equals(replication.get("class")) && "1".equals( replication.get("replication_factor"))) { LOG.warn("running with RF=1, this is not suitable for production. Optimal is 3+"); } String compactionClass = keyspaceMetadata.getTable("traces").getOptions().getCompaction().get("class"); return new Metadata(compactionClass); } static final class Metadata { final String compactionClass; Metadata(String compactionClass) { this.compactionClass = compactionClass; } } static KeyspaceMetadata getKeyspaceMetadata(Session session) { String keyspace = session.getLoggedKeyspace(); Cluster cluster = session.getCluster(); KeyspaceMetadata keyspaceMetadata = cluster.getMetadata().getKeyspace(keyspace); if (keyspaceMetadata == null) { throw new IllegalStateException(String.format( "Cannot read keyspace metadata for give keyspace: %s and cluster: %s", keyspace, cluster.getClusterName())); } return keyspaceMetadata; } static KeyspaceMetadata ensureExists(String keyspace, Session session) { KeyspaceMetadata result = session.getCluster().getMetadata().getKeyspace(keyspace); if (result == null || result.getTable("traces") == null) { LOG.info("Installing schema {}", SCHEMA_RESOURCE); applyCqlFile(keyspace, session, SCHEMA_RESOURCE); // refresh metadata since we've installed the schema result = session.getCluster().getMetadata().getKeyspace(keyspace); } return result; } static void applyCqlFile(String keyspace, Session session, String resource) { try (Reader reader = new InputStreamReader(Schema.class.getResourceAsStream(resource), UTF_8)) { for (String cmd : CharStreams.toString(reader).split(";")) { cmd = cmd.trim().replace(" " + DEFAULT_KEYSPACE, " " + keyspace); if (!cmd.isEmpty()) { session.execute(cmd); } } } catch (IOException ex) { LOG.error(ex.getMessage(), ex); } } @UDT(keyspace = DEFAULT_KEYSPACE + "_udts", name = "trace_id") static final class TraceIdUDT { private long high; private long low; TraceIdUDT() { this.high = 0L; this.low = 0L; } TraceIdUDT(long high, long low) { this.high = high; this.low = low; } Long getHigh() { return high; } long getLow() { return low; } void setHigh(Long high) { this.high = high; } void setLow(long low) { this.low = low; } @Override public boolean equals(Object o) { if (o == this) return true; if (o instanceof TraceIdUDT) { TraceIdUDT that = (TraceIdUDT) o; return (this.high == that.high) && (this.low == that.low); } return false; } @Override public int hashCode() { int h = 1; h *= 1000003; h ^= (high >>> 32) ^ high; h *= 1000003; h ^= (low >>> 32) ^ low; return h; } @Override public String toString() { return toLowerHex(high, low); } } @UDT(keyspace = DEFAULT_KEYSPACE + "_udts", name = "endpoint") static final class EndpointUDT { private String service_name; private InetAddress ipv4; private InetAddress ipv6; private Short port; EndpointUDT() { this.service_name = null; this.ipv4 = null; this.ipv6 = null; this.port = null; } EndpointUDT(Endpoint endpoint) { this.service_name = endpoint.serviceName; this.ipv4 = endpoint.ipv4 == 0 ? null : InetAddresses.fromInteger(endpoint.ipv4); if (endpoint.ipv6 != null && endpoint.ipv6.length == 16) { try { this.ipv6 = Inet6Address.getByAddress(endpoint.ipv6); } catch (UnknownHostException ex) { // We already checked for illegal length, so unexpected! } } this.port = endpoint.port; } public String getService_name() { return service_name; } public InetAddress getIpv4() { return ipv4; } public InetAddress getIpv6() { return ipv6; } public Short getPort() { return port; } public void setService_name(String service_name) { this.service_name = service_name; } public void setIpv4(InetAddress ipv4) { this.ipv4 = ipv4; } public void setIpv6(InetAddress ipv6) { this.ipv6 = ipv6; } public void setPort(short port) { this.port = port; } private Endpoint toEndpoint() { Endpoint.Builder builder = Endpoint.builder().serviceName(service_name).port(port); builder.parseIp(ipv4); builder.parseIp(ipv6); return builder.build(); } } @UDT(keyspace = DEFAULT_KEYSPACE + "_udts", name = "annotation") static final class AnnotationUDT { private long ts; private String v; private EndpointUDT ep; AnnotationUDT() { this.ts = 0; this.v = null; this.ep = null; } AnnotationUDT(Annotation annotation) { this.ts = annotation.timestamp; this.v = annotation.value; this.ep = annotation.endpoint != null ? new EndpointUDT(annotation.endpoint) : null; } public long getTs() { return ts; } public String getV() { return v; } public EndpointUDT getEp() { return ep; } public void setTs(long ts) { this.ts = ts; } public void setV(String v) { this.v = v; } public void setEp(EndpointUDT ep) { this.ep = ep; } Annotation toAnnotation() { Annotation.Builder builder = Annotation.builder().timestamp(ts).value(v); if (null != ep) { builder = builder.endpoint(ep.toEndpoint()); } return builder.build(); } } @UDT(keyspace = DEFAULT_KEYSPACE + "_udts", name = "binary_annotation") static final class BinaryAnnotationUDT { private String k; private ByteBuffer v; private String t; private EndpointUDT ep; BinaryAnnotationUDT() { this.k = null; this.v = null; this.t = null; this.ep = null; } BinaryAnnotationUDT(BinaryAnnotation annotation) { this.k = annotation.key; this.v = annotation.value != null ? ByteBuffer.wrap(annotation.value) : null; this.t = annotation.type.name(); this.ep = annotation.endpoint != null ? new EndpointUDT(annotation.endpoint) : null; } public String getK() { return k; } public ByteBuffer getV() { return v.duplicate(); } public String getT() { return t; } public EndpointUDT getEp() { return ep; } public void setK(String k) { this.k = k; } public void setV(ByteBuffer v) { byte[] bytes = new byte[v.remaining()]; v.duplicate().get(bytes); this.v = ByteBuffer.wrap(bytes); } public void setT(String t) { this.t = t; } public void setEp(EndpointUDT ep) { this.ep = ep; } BinaryAnnotation toBinaryAnnotation() { BinaryAnnotation.Builder builder = BinaryAnnotation.builder() .key(k) .value(v.array()) .type(BinaryAnnotation.Type.valueOf(t)); if (ep != null) builder.endpoint(ep.toEndpoint()); return builder.build(); } } static final class TypeCodecImpl<T> extends TypeCodec<T> { private final TypeCodec<T> codec; public TypeCodecImpl(DataType cqlType, Class<T> javaClass, TypeCodec<T> codec) { super(cqlType, javaClass); this.codec = codec; } @Override public ByteBuffer serialize(T t, ProtocolVersion pv) throws InvalidTypeException { return codec.serialize(t, pv); } @Override public T deserialize(ByteBuffer bb, ProtocolVersion pv) throws InvalidTypeException { return codec.deserialize(bb, pv); } @Override public T parse(String string) throws InvalidTypeException { return codec.parse(string); } @Override public String format(T t) throws InvalidTypeException { return codec.format(t); } } }