org.apache.cassandra.transport.SimpleClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cassandra-all Show documentation
Show all versions of cassandra-all Show documentation
The Apache Cassandra Project develops a highly scalable second-generation distributed database, bringing together Dynamo's fully distributed design and Bigtable's ColumnFamily-based data model.
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.transport;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.primitives.Ints;
import org.apache.cassandra.transport.ClientResourceLimits.Overload;
import org.apache.cassandra.utils.concurrent.NonBlockingRateLimiter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.ssl.SslContext;
import io.netty.util.concurrent.Promise; // checkstyle: permit this import
import io.netty.util.concurrent.PromiseCombiner;
import io.netty.util.internal.logging.InternalLoggerFactory;
import io.netty.util.internal.logging.Slf4JLoggerFactory;
import org.apache.cassandra.concurrent.NamedThreadFactory;
import org.apache.cassandra.config.EncryptionOptions;
import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.net.*;
import org.apache.cassandra.security.ISslContextFactory;
import org.apache.cassandra.security.SSLFactory;
import org.apache.cassandra.transport.messages.*;
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
import static org.apache.cassandra.net.SocketFactory.newSslHandler;
import static org.apache.cassandra.transport.CQLMessageHandler.envelopeSize;
import static org.apache.cassandra.transport.Flusher.MAX_FRAMED_PAYLOAD_SIZE;
import static org.apache.cassandra.transport.PipelineConfigurator.SSL_FACTORY_CONTEXT_DESCRIPTION;
import static org.apache.cassandra.utils.concurrent.NonBlockingRateLimiter.NO_OP_LIMITER;
import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis;
import static org.apache.cassandra.utils.concurrent.BlockingQueues.newBlockingQueue;
public class SimpleClient implements Closeable
{
public static final int TIMEOUT_SECONDS = 10;
static
{
InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory());
}
private static final Logger logger = LoggerFactory.getLogger(SimpleClient.class);
public final String host;
public final int port;
private final EncryptionOptions encryptionOptions;
private final int largeMessageThreshold;
protected final ResponseHandler responseHandler = new ResponseHandler();
protected final Connection.Tracker tracker = new ConnectionTracker();
protected final ProtocolVersion version;
// We don't track connection really, so we don't need one Connection per channel
protected Connection connection;
protected Bootstrap bootstrap;
protected Channel channel;
protected ChannelFuture lastWriteFuture;
protected String compression;
public static class Builder
{
private final String host;
private final int port;
private EncryptionOptions encryptionOptions = new EncryptionOptions();
private ProtocolVersion version = ProtocolVersion.CURRENT;
private boolean useBeta = false;
private int largeMessageThreshold = FrameEncoder.Payload.MAX_SIZE;
private Builder(String host, int port)
{
this.host = host;
this.port = port;
}
public Builder encryption(EncryptionOptions options)
{
this.encryptionOptions = options;
return this;
}
public Builder useBeta()
{
this.useBeta = true;
return this;
}
public Builder protocolVersion(ProtocolVersion version)
{
this.version = version;
return this;
}
public Builder largeMessageThreshold(int bytes)
{
largeMessageThreshold = bytes;
return this;
}
public SimpleClient build()
{
if (version.isBeta() && !useBeta)
throw new IllegalArgumentException(String.format("Beta version of server used (%s), but USE_BETA flag is not set", version));
return new SimpleClient(this);
}
}
public static Builder builder(String host, int port)
{
return new Builder(host, port);
}
private SimpleClient(Builder builder)
{
this.host = builder.host;
this.port = builder.port;
this.version = builder.version;
this.encryptionOptions = builder.encryptionOptions.applyConfig();
this.largeMessageThreshold = builder.largeMessageThreshold;
}
public SimpleClient(String host, int port, ProtocolVersion version, EncryptionOptions encryptionOptions)
{
this(host, port, version, false, encryptionOptions);
}
public SimpleClient(String host, int port, EncryptionOptions encryptionOptions)
{
this(host, port, ProtocolVersion.CURRENT, encryptionOptions);
}
public SimpleClient(String host, int port, ProtocolVersion version)
{
this(host, port, version, new EncryptionOptions());
}
public SimpleClient(String host, int port, ProtocolVersion version, boolean useBeta, EncryptionOptions encryptionOptions)
{
this.host = host;
this.port = port;
if (version.isBeta() && !useBeta)
throw new IllegalArgumentException(String.format("Beta version of server used (%s), but USE_BETA flag is not set", version));
this.version = version;
this.encryptionOptions = new EncryptionOptions(encryptionOptions).applyConfig();
this.largeMessageThreshold = FrameEncoder.Payload.MAX_SIZE -
Math.max(FrameEncoderCrc.HEADER_AND_TRAILER_LENGTH,
FrameEncoderLZ4.HEADER_AND_TRAILER_LENGTH);
}
public SimpleClient(String host, int port)
{
this(host, port, new EncryptionOptions());
}
public SimpleClient connect(boolean useCompression) throws IOException
{
return connect(useCompression, false);
}
public SimpleClient connect(boolean useCompression, boolean throwOnOverload) throws IOException
{
establishConnection();
Map options = new HashMap<>();
options.put(StartupMessage.CQL_VERSION, "3.0.0");
if (throwOnOverload)
options.put(StartupMessage.THROW_ON_OVERLOAD, "1");
connection.setThrowOnOverload(throwOnOverload);
if (useCompression)
{
options.put(StartupMessage.COMPRESSION, "LZ4");
connection.setCompressor(Compressor.LZ4Compressor.instance);
}
execute(new StartupMessage(options));
return this;
}
public void setEventHandler(EventHandler eventHandler)
{
responseHandler.eventHandler = eventHandler;
}
@VisibleForTesting
void establishConnection() throws IOException
{
// Configure the client.
bootstrap = new Bootstrap()
.group(new NioEventLoopGroup(new NamedThreadFactory("SimpleClient-nioEventLoopGroup")))
.channel(io.netty.channel.socket.nio.NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true);
// Configure the pipeline factory.
if(encryptionOptions.getEnabled())
{
bootstrap.handler(new SecureInitializer(largeMessageThreshold));
}
else
{
bootstrap.handler(new Initializer(largeMessageThreshold));
}
ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port));
// Wait until the connection attempt succeeds or fails.
channel = future.awaitUninterruptibly().channel();
if (!future.isSuccess())
{
bootstrap.group().shutdownGracefully();
throw new IOException("Connection Error", future.cause());
}
}
public ResultMessage execute(String query, ConsistencyLevel consistency)
{
return execute(query, Collections.emptyList(), consistency);
}
public ResultMessage execute(String query, List values, ConsistencyLevel consistencyLevel)
{
Message.Response msg = execute(new QueryMessage(query, QueryOptions.forInternalCalls(consistencyLevel, values)));
assert msg instanceof ResultMessage;
return (ResultMessage)msg;
}
public ResultMessage.Prepared prepare(String query)
{
Message.Response msg = execute(new PrepareMessage(query, null));
assert msg instanceof ResultMessage.Prepared;
return (ResultMessage.Prepared)msg;
}
public ResultMessage executePrepared(ResultMessage.Prepared prepared, List values, ConsistencyLevel consistency)
{
Message.Response msg = execute(new ExecuteMessage(prepared.statementId, prepared.resultMetadataId, QueryOptions.forInternalCalls(consistency, values)));
assert msg instanceof ResultMessage;
return (ResultMessage)msg;
}
public void close()
{
// Wait until all messages are flushed before closing the channel.
if (lastWriteFuture != null)
lastWriteFuture.awaitUninterruptibly();
// Close the connection. Make sure the close operation ends because
// all I/O operations are asynchronous in Netty.
channel.close().awaitUninterruptibly();
// Shut down all thread pools to exit.
bootstrap.group().shutdownGracefully();
}
public Message.Response execute(Message.Request request)
{
return execute(request, true);
}
public Message.Response execute(Message.Request request, boolean throwOnErrorResponse)
{
try
{
request.attach(connection);
lastWriteFuture = channel.writeAndFlush(Collections.singletonList(request));
Message.Response msg = responseHandler.responses.poll(TIMEOUT_SECONDS, TimeUnit.SECONDS);
if (msg == null)
throw new RuntimeException("timeout");
if (throwOnErrorResponse && msg instanceof ErrorMessage)
throw new RuntimeException((Throwable)((ErrorMessage)msg).error);
return msg;
}
catch (InterruptedException e)
{
throw new UncheckedInterruptedException(e);
}
}
public Map execute(List requests)
{
try
{
Map rrMap = new HashMap<>();
if (version.isGreaterOrEqualTo(ProtocolVersion.V5))
{
for (int i = 0; i < requests.size(); i++)
{
Message.Request message = requests.get(i);
message.setStreamId(i);
message.attach(connection);
}
lastWriteFuture = channel.writeAndFlush(requests);
long deadline = currentTimeMillis() + TimeUnit.SECONDS.toMillis(TIMEOUT_SECONDS);
for (int i = 0; i < requests.size(); i++)
{
Message.Response msg = responseHandler.responses.poll(deadline - currentTimeMillis(), TimeUnit.MILLISECONDS);
if (msg == null)
throw new RuntimeException("timeout");
if (msg instanceof ErrorMessage)
throw new RuntimeException((Throwable) ((ErrorMessage) msg).error);
rrMap.put(requests.get(msg.getStreamId()), msg);
}
}
else
{
// V4 doesn't support batching
for (Message.Request request : requests)
rrMap.put(request, execute(request));
}
return rrMap;
}
catch (InterruptedException e)
{
throw new UncheckedInterruptedException(e);
}
}
public interface EventHandler
{
void onEvent(Event event);
}
public static class SimpleEventHandler implements EventHandler
{
public final BlockingQueue queue = newBlockingQueue();
public void onEvent(Event event)
{
queue.add(event);
}
}
private static class ConnectionTracker implements Connection.Tracker
{
public void addConnection(Channel ch, Connection connection) {}
}
private static class HandlerNames
{
private static final String ENVELOPE_DECODER = "envelopeDecoder";
private static final String ENVELOPE_ENCODER = "envelopeEncoder";
private static final String COMPRESSOR = "compressor";
private static final String DECOMPRESSOR = "decompressor";
private static final String MESSAGE_DECODER = "messageDecoder";
private static final String MESSAGE_ENCODER = "messageEncoder";
private static final String INITIAL_HANDLER = "intitialHandler";
private static final String RESPONSE_HANDLER = "responseHandler";
private static final String FRAME_DECODER = "frameDecoder";
private static final String FRAME_ENCODER = "frameEncoder";
private static final String PROCESSOR = "processor";
}
private static class InitialHandler extends MessageToMessageDecoder
{
final ProtocolVersion version;
final ResponseHandler responseHandler;
final int largeMessageThreshold;
InitialHandler(ProtocolVersion version, ResponseHandler responseHandler, int largeMessageThreshold)
{
this.version = version;
this.responseHandler = responseHandler;
this.largeMessageThreshold = largeMessageThreshold;
}
protected void decode(ChannelHandlerContext ctx, Envelope response, List