/**
* 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;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.client.RingCache;
import org.apache.cassandra.dht.RandomPartitioner;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.thrift.*;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.WrappedRunnable;
import org.apache.thrift.TException;
import org.apache.cassandra.CassandraServiceController.Failure;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
public class MutationTest extends TestBase
{
private static final Logger logger = LoggerFactory.getLogger(MutationTest.class);
@Test
public void testInsert() throws Exception
{
List<InetAddress> hosts = controller.getHosts();
final String keyspace = "TestInsert";
addKeyspace(keyspace, 3);
Cassandra.Client client = controller.createClient(hosts.get(0));
client.set_keyspace(keyspace);
ByteBuffer key = newKey();
insert(client, key, "Standard1", "c1", "v1", 0, ConsistencyLevel.ONE);
insert(client, key, "Standard1", "c2", "v2", 0, ConsistencyLevel.ONE);
// block until the column is available
new Get(client, "Standard1", key).name("c1").value("v1").perform(ConsistencyLevel.ONE);
new Get(client, "Standard1", key).name("c2").value("v2").perform(ConsistencyLevel.ONE);
List<ColumnOrSuperColumn> coscs = get_slice(client, key, "Standard1", ConsistencyLevel.ONE);
assertColumnEqual("c1", "v1", 0, coscs.get(0).column);
assertColumnEqual("c2", "v2", 0, coscs.get(1).column);
}
@Test
public void testWriteAllReadOne() throws Exception
{
List<InetAddress> hosts = controller.getHosts();
Cassandra.Client client = controller.createClient(hosts.get(0));
final String keyspace = "TestWriteAllReadOne";
addKeyspace(keyspace, 3);
client.set_keyspace(keyspace);
ByteBuffer key = newKey();
insert(client, key, "Standard1", "c1", "v1", 0, ConsistencyLevel.ALL);
// should be instantly available
assertColumnEqual("c1", "v1", 0, getColumn(client, key, "Standard1", "c1", ConsistencyLevel.ONE));
List<InetAddress> endpoints = endpointsForKey(hosts.get(0), key, keyspace);
InetAddress coordinator = nonEndpointForKey(hosts.get(0), key, keyspace);
Failure failure = controller.failHosts(endpoints.subList(1, endpoints.size()));
try {
client = controller.createClient(coordinator);
client.set_keyspace(keyspace);
new Get(client, "Standard1", key).name("c1").value("v1")
.perform(ConsistencyLevel.ONE);
new Insert(client, "Standard1", key).name("c3").value("v3")
.expecting(UnavailableException.class).perform(ConsistencyLevel.ALL);
} finally {
failure.resolve();
Thread.sleep(10000);
}
}
@Test
public void testWriteQuorumReadQuorum() throws Exception
{
List<InetAddress> hosts = controller.getHosts();
Cassandra.Client client = controller.createClient(hosts.get(0));
final String keyspace = "TestWriteQuorumReadQuorum";
addKeyspace(keyspace, 3);
client.set_keyspace(keyspace);
ByteBuffer key = newKey();
// with quorum-1 nodes up
List<InetAddress> endpoints = endpointsForKey(hosts.get(0), key, keyspace);
InetAddress coordinator = nonEndpointForKey(hosts.get(0), key, keyspace);
Failure failure = controller.failHosts(endpoints.subList(1, endpoints.size())); //kill all but one nodes
client = controller.createClient(coordinator);
client.set_keyspace(keyspace);
try {
new Insert(client, "Standard1", key).name("c1").value("v1")
.expecting(UnavailableException.class).perform(ConsistencyLevel.QUORUM);
} finally {
failure.resolve();
}
// with all nodes up
new Insert(client, "Standard1", key).name("c2").value("v2").perform(ConsistencyLevel.QUORUM);
failure = controller.failHosts(endpoints.get(0));
try {
new Get(client, "Standard1", key).name("c2").value("v2").perform(ConsistencyLevel.QUORUM);
} finally {
failure.resolve();
Thread.sleep(10000);
}
}
@Test
public void testWriteOneReadAll() throws Exception
{
List<InetAddress> hosts = controller.getHosts();
Cassandra.Client client = controller.createClient(hosts.get(0));
final String keyspace = "TestWriteOneReadAll";
addKeyspace(keyspace, 3);
client.set_keyspace(keyspace);
ByteBuffer key = newKey();
List<InetAddress> endpoints = endpointsForKey(hosts.get(0), key, keyspace);
InetAddress coordinator = nonEndpointForKey(hosts.get(0), key, keyspace);
client = controller.createClient(coordinator);
client.set_keyspace(keyspace);
insert(client, key, "Standard1", "c1", "v1", 0, ConsistencyLevel.ONE);
assertColumnEqual("c1", "v1", 0, getColumn(client, key, "Standard1", "c1", ConsistencyLevel.ALL));
// with each of HH, read repair and proactive repair:
// with one node up
// write with one (success)
// read with all (failure)
// bring nodes up
// repair
// read with all (success)
Failure failure = controller.failHosts(endpoints);
try {
new Insert(client, "Standard1", key).name("c2").value("v2")
.expecting(UnavailableException.class).perform(ConsistencyLevel.ONE);
} finally {
failure.resolve();
}
}
protected void insert(Cassandra.Client client, ByteBuffer key, String cf, String name, String value, long timestamp, ConsistencyLevel cl)
throws InvalidRequestException, UnavailableException, TimedOutException, TException
{
Column col = new Column(
ByteBufferUtil.bytes(name),
ByteBufferUtil.bytes(value),
timestamp
);
client.insert(key, new ColumnParent(cf), col, cl);
}
protected Column getColumn(Cassandra.Client client, ByteBuffer key, String cf, String col, ConsistencyLevel cl)
throws InvalidRequestException, UnavailableException, TimedOutException, TException, NotFoundException
{
ColumnPath cpath = new ColumnPath(cf);
cpath.setColumn(col.getBytes());
return client.get(key, cpath, cl).column;
}
protected class Get extends RetryingAction
{
public Get(Cassandra.Client client, String cf, ByteBuffer key)
{
super(client, cf, key);
}
public void tryPerformAction(ConsistencyLevel cl) throws Exception
{
assertColumnEqual(name, value, timestamp, getColumn(client, key, cf, name, cl));
}
}
protected class Insert extends RetryingAction
{
public Insert(Cassandra.Client client, String cf, ByteBuffer key)
{
super(client, cf, key);
}
public void tryPerformAction(ConsistencyLevel cl) throws Exception
{
insert(client, key, cf, name, value, timestamp, cl);
}
}
/** Performs an action repeatedly until timeout, success or failure. */
protected abstract class RetryingAction
{
protected Cassandra.Client client;
protected String cf;
protected ByteBuffer key;
protected String name;
protected String value;
protected long timestamp;
private Set<Class<Exception>> expected = new HashSet<Class<Exception>>();
private long timeout = StorageService.RING_DELAY;
public RetryingAction(Cassandra.Client client, String cf, ByteBuffer key)
{
this.client = client;
this.cf = cf;
this.key = key;
this.timestamp = 0;
}
public RetryingAction name(String name)
{
this.name = name; return this;
}
/** The value to expect for the return column, or null to expect the column to be missing. */
public RetryingAction value(String value)
{
this.value = value; return this;
}
/** The total time to allow before failing. */
public RetryingAction timeout(long timeout)
{
this.timeout = timeout; return this;
}
/** The expected timestamp of the returned column. */
public RetryingAction timestamp(long timestamp)
{
this.timestamp = timestamp; return this;
}
/** The exception classes that indicate success. */
public RetryingAction expecting(Class... tempExceptions)
{
this.expected.clear();
for (Class exclass : tempExceptions)
expected.add((Class<Exception>)exclass);
return this;
}
public void perform(ConsistencyLevel cl) throws AssertionError
{
long deadline = System.currentTimeMillis() + timeout;
int attempts = 0;
String template = "%s for " + this + " after %d attempt(s) with %d ms to spare.";
Exception e = null;
while(deadline > System.currentTimeMillis())
{
try
{
attempts++;
tryPerformAction(cl);
logger.info(String.format(template, "Succeeded", attempts, deadline - System.currentTimeMillis()));
return;
}
catch (Exception ex)
{
e = ex;
if (!expected.contains(ex.getClass()))
continue;
logger.info(String.format(template, "Caught expected exception: " + e, attempts, deadline - System.currentTimeMillis()));
return;
}
}
String err = String.format(template, "Caught unexpected: " + e, attempts, deadline - System.currentTimeMillis());
logger.error(err);
throw new AssertionError(err);
}
public String toString()
{
return this.getClass() + "(" + key + "," + name + ")";
}
protected abstract void tryPerformAction(ConsistencyLevel cl) throws Exception;
}
protected List<ColumnOrSuperColumn> get_slice(Cassandra.Client client, ByteBuffer key, String cf, ConsistencyLevel cl)
throws InvalidRequestException, UnavailableException, TimedOutException, TException
{
SlicePredicate sp = new SlicePredicate();
sp.setSlice_range(
new SliceRange(
ByteBuffer.wrap(new byte[0]),
ByteBuffer.wrap(new byte[0]),
false,
1000
)
);
return client.get_slice(key, new ColumnParent(cf), sp, cl);
}
protected void assertColumnEqual(String name, String value, long timestamp, Column col)
{
assertEquals(ByteBufferUtil.bytes(name), col.name);
assertEquals(ByteBufferUtil.bytes(value), col.value);
assertEquals(timestamp, col.timestamp);
}
protected List<InetAddress> endpointsForKey(InetAddress seed, ByteBuffer key, String keyspace)
throws IOException
{
RingCache ring = new RingCache(keyspace, new RandomPartitioner(), seed.getHostAddress(), 9160);
List<InetAddress> privateendpoints = ring.getEndpoint(key);
List<InetAddress> endpoints = new ArrayList<InetAddress>();
for (InetAddress endpoint : privateendpoints)
{
endpoints.add(controller.getPublicHost(endpoint));
}
return endpoints;
}
protected InetAddress nonEndpointForKey(InetAddress seed, ByteBuffer key, String keyspace)
throws IOException
{
List<InetAddress> endpoints = endpointsForKey(seed, key, keyspace);
for (InetAddress host : controller.getHosts())
{
if (!endpoints.contains(host))
{
return host;
}
}
return null;
}
protected ByteBuffer newKey()
{
return ByteBufferUtil.bytes(String.format("test.key.%d", System.currentTimeMillis()));
}
}