diff --git a/arangodb/.gitignore b/arangodb/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ae3c1726048cd06b9a143e0376ed46dd9b9a8d53 --- /dev/null +++ b/arangodb/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/arangodb/README.md b/arangodb/README.md new file mode 100644 index 0000000000000000000000000000000000000000..001892fd912ac6dcfe8f134272a52c3c91caf109 --- /dev/null +++ b/arangodb/README.md @@ -0,0 +1,93 @@ +<!-- +Copyright (c) 2012 - 2015 YCSB contributors. All rights reserved. + +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. See accompanying +LICENSE file. +--> + +## Quick Start + +This section describes how to run YCSB on ArangoDB. + +### 1. Start ArangoDB +See https://docs.arangodb.com/Installing/index.html + +### 2. Install Java and Maven + +Go to http://www.oracle.com/technetwork/java/javase/downloads/index.html + +and get the url to download the rpm into your server. For example: + + wget http://download.oracle.com/otn-pub/java/jdk/7u40-b43/jdk-7u40-linux-x64.rpm?AuthParam=11232426132 -o jdk-7u40-linux-x64.rpm + rpm -Uvh jdk-7u40-linux-x64.rpm + +Or install via yum/apt-get + + sudo yum install java-devel + +Download MVN from http://maven.apache.org/download.cgi + + wget http://ftp.heanet.ie/mirrors/www.apache.org/dist/maven/maven-3/3.1.1/binaries/apache-maven-3.1.1-bin.tar.gz + sudo tar xzf apache-maven-*-bin.tar.gz -C /usr/local + cd /usr/local + sudo ln -s apache-maven-* maven + sudo vi /etc/profile.d/maven.sh + +Add the following to `maven.sh` + + export M2_HOME=/usr/local/maven + export PATH=${M2_HOME}/bin:${PATH} + +Reload bash and test mvn + + bash + mvn -version + +### 3. Set Up YCSB + +Clone this YCSB source code: + + git clone https://github.com/brianfrankcooper/YCSB.git + +### 4. Run YCSB + +Now you are ready to run! First, drop the existing collection: "usertable" under database "ycsb": + + db._collection("usertable").drop() + +Then, load the data: + + ./bin/ycsb load arangodb -s -P workloads/workloada -p arangodb.ip=xxx -p arangodb.port=xxx + +Then, run the workload: + + ./bin/ycsb run arangodb -s -P workloads/workloada -p arangodb.ip=xxx -p arangodb.port=xxx + +See the next section for the list of configuration parameters for ArangoDB. + +## ArangoDB Configuration Parameters + +- `arangodb.ip` + - Default value is `localhost` + +- `arangodb.port` + - Default value is `8529`. + +- `arangodb.waitForSync` + - Default value is `true`. + +- `arangodb.transactionUpdate` + - Default value is `false`. + +- `arangodb.dropDBBeforeRun` + - Default value is `false`. diff --git a/arangodb/conf/logback.xml b/arangodb/conf/logback.xml new file mode 100644 index 0000000000000000000000000000000000000000..95668c700c6c7850bf3294d49fb12e2601c1441d --- /dev/null +++ b/arangodb/conf/logback.xml @@ -0,0 +1,31 @@ +<!-- +Copyright (c) 2012 YCSB contributors. All rights reserved. + +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. See accompanying +LICENSE file. +--> + +<configuration> + + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <!-- encoders are assigned the type + ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --> + <encoder> + <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> + </encoder> + </appender> + + <root level="info"> + <appender-ref ref="STDOUT" /> + </root> +</configuration> diff --git a/arangodb/pom.xml b/arangodb/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..717224d69338bdd4d3d92aa2472c7da6d1c91ca0 --- /dev/null +++ b/arangodb/pom.xml @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Copyright (c) 2012 - 2016 YCSB contributors. All rights reserved. + +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. See accompanying +LICENSE file. +--> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.yahoo.ycsb</groupId> + <artifactId>binding-parent</artifactId> + <version>0.12.0-SNAPSHOT</version> + <relativePath>../binding-parent</relativePath> + </parent> + + <artifactId>arangodb-binding</artifactId> + <name>ArangoDB Binding</name> + <packaging>jar</packaging> + + <dependencies> + <dependency> + <groupId>com.arangodb</groupId> + <artifactId>arangodb-java-driver</artifactId> + <version>${arangodb.version}</version> + </dependency> + <dependency> + <groupId>com.yahoo.ycsb</groupId> + <artifactId>core</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>1.7.13</version> + <type>jar</type> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + <version>1.1.3</version> + <type>jar</type> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-core</artifactId> + <version>1.1.3</version> + <type>jar</type> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.12</version> + <scope>test</scope> + </dependency> + </dependencies> +</project> diff --git a/arangodb/src/main/java/com/yahoo/ycsb/db/ArangoDBClient.java b/arangodb/src/main/java/com/yahoo/ycsb/db/ArangoDBClient.java new file mode 100644 index 0000000000000000000000000000000000000000..1a9d185f53009ddceee5e841ca779c7201cba86e --- /dev/null +++ b/arangodb/src/main/java/com/yahoo/ycsb/db/ArangoDBClient.java @@ -0,0 +1,466 @@ +/** + * Copyright (c) 2012 - 2015 YCSB contributors. All rights reserved. + * + * 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. See accompanying + * LICENSE file. + */ +package com.yahoo.ycsb.db; + +import com.arangodb.ArangoConfigure; +import com.arangodb.ArangoDriver; +import com.arangodb.ArangoException; +import com.arangodb.ArangoHost; +import com.arangodb.DocumentCursor; +import com.arangodb.ErrorNums; +import com.arangodb.entity.BaseDocument; +import com.arangodb.entity.DocumentEntity; +import com.arangodb.entity.EntityFactory; +import com.arangodb.entity.TransactionEntity; +import com.arangodb.util.MapBuilder; + +import com.yahoo.ycsb.DB; +import com.yahoo.ycsb.Status; +import com.yahoo.ycsb.DBException; +import com.yahoo.ycsb.ByteIterator; +import com.yahoo.ycsb.StringByteIterator; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.Vector; +import java.util.concurrent.atomic.AtomicInteger; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * ArangoDB binding for YCSB framework using the ArangoDB Inc. <a + * href="https://github.com/arangodb/arangodb-java-driver">driver</a> + * <p> + * See the <code>README.md</code> for configuration information. + * </p> + * + * @see <a href="https://github.com/arangodb/arangodb-java-driver">ArangoDB Inc. + * driver</a> + */ +public class ArangoDBClient extends DB { + + private static Logger logger = LoggerFactory.getLogger(ArangoDBClient.class); + + /** + * The database name to access. + */ + private static String databaseName = "ycsb"; + + /** + * Count the number of times initialized to teardown on the last + * {@link #cleanup()}. + */ + private static final AtomicInteger INIT_COUNT = new AtomicInteger(0); + + /** ArangoDB Driver related, Singleton. */ + private static ArangoDriver arangoDriver; + private static Boolean dropDBBeforeRun; + private static Boolean waitForSync = true; + private static Boolean transactionUpdate = false; + + /** + * Initialize any state for this DB. Called once per DB instance; there is + * one DB instance per client thread. + * + * Actually, one client process will share one DB instance here.(Coincide to + * mongoDB driver) + */ + @Override + public void init() throws DBException { + INIT_COUNT.incrementAndGet(); + synchronized (ArangoDBClient.class) { + if (arangoDriver != null) { + return; + } + + Properties props = getProperties(); + + // Set the DB address + String ip = props.getProperty("arangodb.ip", "localhost"); + String portStr = props.getProperty("arangodb.port", "8529"); + int port = Integer.parseInt(portStr); + + // If clear db before run + String dropDBBeforeRunStr = props.getProperty("arangodb.dropDBBeforeRun", "false"); + dropDBBeforeRun = Boolean.parseBoolean(dropDBBeforeRunStr); + + // Set the sync mode + String waitForSyncStr = props.getProperty("arangodb.waitForSync", "false"); + waitForSync = Boolean.parseBoolean(waitForSyncStr); + + // Set if transaction for update + String transactionUpdateStr = props.getProperty("arangodb.transactionUpdate", "false"); + transactionUpdate = Boolean.parseBoolean(transactionUpdateStr); + + // Init ArangoDB connection + try { + ArangoConfigure arangoConfigure = new ArangoConfigure(); + arangoConfigure.setArangoHost(new ArangoHost(ip, port)); + arangoConfigure.init(); + arangoDriver = new ArangoDriver(arangoConfigure); + } catch (Exception e) { + logger.error("Failed to initialize ArangoDB", e); + System.exit(-1); + } + + // Init the database + if (dropDBBeforeRun) { + // Try delete first + try { + arangoDriver.deleteDatabase(databaseName); + } catch (ArangoException e) { + if (e.getErrorNumber() != ErrorNums.ERROR_ARANGO_DATABASE_NOT_FOUND) { + logger.error("Failed to delete database: {} with ex: {}", databaseName, e.toString()); + System.exit(-1); + } else { + logger.info("Fail to delete DB, already deleted: {}", databaseName); + } + } + } + try { + arangoDriver.createDatabase(databaseName); + logger.info("Database created: " + databaseName); + } catch (ArangoException e) { + if (e.getErrorNumber() != ErrorNums.ERROR_ARANGO_DUPLICATE_NAME) { + logger.error("Failed to create database: {} with ex: {}", databaseName, e.toString()); + System.exit(-1); + } else { + logger.info("DB already exists: {}", databaseName); + } + } + // Always set the default db + arangoDriver.setDefaultDatabase(databaseName); + logger.info("ArangoDB client connection created to {}:{}", ip, port); + + // Log the configuration + logger.info("Arango Configuration: dropDBBeforeRun: {}; address: {}:{}; databaseName: {};" + + " waitForSync: {}; transactionUpdate: {};", + dropDBBeforeRun, ip, port, databaseName, waitForSync, transactionUpdate); + } + } + + /** + * Cleanup any state for this DB. Called once per DB instance; there is one + * DB instance per client thread. + * + * Actually, one client process will share one DB instance here.(Coincide to + * mongoDB driver) + */ + @Override + public void cleanup() throws DBException { + if (INIT_COUNT.decrementAndGet() == 0) { + arangoDriver = null; + logger.info("Local cleaned up."); + } + } + + /** + * Insert a record in the database. Any field/value pairs in the specified + * values HashMap will be written into the record with the specified record + * key. + * + * @param table + * The name of the table + * @param key + * The record key of the record to insert. + * @param values + * A HashMap of field/value pairs to insert in the record + * @return Zero on success, a non-zero error code on error. See the + * {@link DB} class's description for a discussion of error codes. + */ + @Override + public Status insert(String table, String key, HashMap<String, ByteIterator> values) { + try { + BaseDocument toInsert = new BaseDocument(key); + for (Map.Entry<String, ByteIterator> entry : values.entrySet()) { + toInsert.addAttribute(entry.getKey(), byteIteratorToString(entry.getValue())); + } + arangoDriver.createDocument(table, toInsert, true/*create collection if not exist*/, + waitForSync); + return Status.OK; + } catch (ArangoException e) { + if (e.getErrorNumber() != ErrorNums.ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED) { + logger.error("Fail to insert: {} {} with ex {}", table, key, e.toString()); + } else { + logger.debug("Trying to create document with duplicate key: {} {}", table, key); + return Status.BAD_REQUEST; + } + } catch (RuntimeException e) { + logger.error("Exception while trying insert {} {} with ex {}", table, key, e.toString()); + } + return Status.ERROR; + } + + /** + * Read a record from the database. Each field/value pair from the result + * will be stored in a HashMap. + * + * @param table + * The name of the table + * @param key + * The record key of the record to read. + * @param fields + * The list of fields to read, or null for all of them + * @param result + * A HashMap of field/value pairs for the result + * @return Zero on success, a non-zero error code on error or "not found". + */ + @SuppressWarnings("unchecked") + @Override + public Status read(String table, String key, Set<String> fields, HashMap<String, ByteIterator> result) { + try { + DocumentEntity<BaseDocument> targetDoc = arangoDriver.getDocument(table, key, BaseDocument.class); + BaseDocument aDocument = targetDoc.getEntity(); + if (!this.fillMap(result, aDocument.getProperties(), fields)) { + return Status.ERROR; + } + return Status.OK; + } catch (ArangoException e) { + if (e.getErrorNumber() != ErrorNums.ERROR_ARANGO_DOCUMENT_NOT_FOUND) { + logger.error("Fail to read: {} {} with ex {}", table, key, e.toString()); + } else { + logger.debug("Trying to read document not exist: {} {}", table, key); + return Status.NOT_FOUND; + } + } catch (RuntimeException e) { + logger.error("Exception while trying read {} {} with ex {}", table, key, e.toString()); + } + return Status.ERROR; + } + + /** + * Update a record in the database. Any field/value pairs in the specified + * values HashMap will be written into the record with the specified record + * key, overwriting any existing values with the same field name. + * + * @param table + * The name of the table + * @param key + * The record key of the record to write. + * @param values + * A HashMap of field/value pairs to update in the record + * @return Zero on success, a non-zero error code on error. See this class's + * description for a discussion of error codes. + */ + @Override + public Status update(String table, String key, HashMap<String, ByteIterator> values) { + try { + + if (!transactionUpdate) { + BaseDocument updateDoc = new BaseDocument(); + for (String field : values.keySet()) { + updateDoc.addAttribute(field, byteIteratorToString(values.get(field))); + } + arangoDriver.updateDocument(table, key, updateDoc); + return Status.OK; + } else { + // id for documentHandle + String transactionAction = "function (id) {" + // use internal database functions + + "var db = require('internal').db;" + // collection.update(document, data, overwrite, keepNull, waitForSync) + + String.format("db._update(id, %s, true, false, %s);}", + mapToJson(values), Boolean.toString(waitForSync).toLowerCase()); + TransactionEntity transaction = arangoDriver.createTransaction(transactionAction); + transaction.addWriteCollection(table); + transaction.setParams(createDocumentHandle(table, key)); + arangoDriver.executeTransaction(transaction); + return Status.OK; + } + } catch (ArangoException e) { + if (e.getErrorNumber() != ErrorNums.ERROR_ARANGO_DOCUMENT_NOT_FOUND) { + logger.error("Fail to update: {} {} with ex {}", table, key, e.toString()); + } else { + logger.debug("Trying to update document not exist: {} {}", table, key); + return Status.NOT_FOUND; + } + } catch (RuntimeException e) { + logger.error("Exception while trying update {} {} with ex {}", table, key, e.toString()); + } + return Status.ERROR; + } + + /** + * Delete a record from the database. + * + * @param table + * The name of the table + * @param key + * The record key of the record to delete. + * @return Zero on success, a non-zero error code on error. See the + * {@link DB} class's description for a discussion of error codes. + */ + @Override + public Status delete(String table, String key) { + try { + arangoDriver.deleteDocument(table, key); + return Status.OK; + } catch (ArangoException e) { + if (e.getErrorNumber() != ErrorNums.ERROR_ARANGO_DOCUMENT_NOT_FOUND) { + logger.error("Fail to delete: {} {} with ex {}", table, key, e.toString()); + } else { + logger.debug("Trying to delete document not exist: {} {}", table, key); + return Status.NOT_FOUND; + } + } catch (RuntimeException e) { + logger.error("Exception while trying delete {} {} with ex {}", table, key, e.toString()); + } + return Status.ERROR; + } + + /** + * Perform a range scan for a set of records in the database. Each + * field/value pair from the result will be stored in a HashMap. + * + * @param table + * The name of the table + * @param startkey + * The record key of the first record to read. + * @param recordcount + * The number of records to read + * @param fields + * The list of fields to read, or null for all of them + * @param result + * A Vector of HashMaps, where each HashMap is a set field/value + * pairs for one record + * @return Zero on success, a non-zero error code on error. See the + * {@link DB} class's description for a discussion of error codes. + */ + @Override + public Status scan(String table, String startkey, int recordcount, Set<String> fields, + Vector<HashMap<String, ByteIterator>> result) { + DocumentCursor<BaseDocument> cursor = null; + try { + String aqlQuery = String.format( + "FOR target IN %s FILTER target._key >= @key SORT target._key ASC LIMIT %d RETURN %s ", table, + recordcount, constructReturnForAQL(fields, "target")); + + Map<String, Object> bindVars = new MapBuilder().put("key", startkey).get(); + cursor = arangoDriver.executeDocumentQuery(aqlQuery, bindVars, null, BaseDocument.class); + Iterator<BaseDocument> iterator = cursor.entityIterator(); + while (iterator.hasNext()) { + BaseDocument aDocument = iterator.next(); + HashMap<String, ByteIterator> aMap = new HashMap<String, ByteIterator>(aDocument.getProperties().size()); + if (!this.fillMap(aMap, aDocument.getProperties())) { + return Status.ERROR; + } + result.add(aMap); + } + return Status.OK; + } catch (Exception e) { + logger.error("Exception while trying scan {} {} {} with ex {}", table, startkey, recordcount, e.toString()); + } finally { + if (cursor != null) { + try { + cursor.close(); + } catch (ArangoException e) { + logger.error("Fail to close cursor", e); + } + } + } + return Status.ERROR; + } + + private String createDocumentHandle(String collectionName, String documentKey) throws ArangoException { + validateCollectionName(collectionName); + return collectionName + "/" + documentKey; + } + + private void validateCollectionName(String name) throws ArangoException { + if (name.indexOf('/') != -1) { + throw new ArangoException("does not allow '/' in name."); + } + } + + + private String constructReturnForAQL(Set<String> fields, String targetName) { + // Construct the AQL query string. + String resultDes = targetName; + if (fields != null && fields.size() != 0) { + StringBuilder builder = new StringBuilder("{"); + for (String field : fields) { + builder.append(String.format("\n\"%s\" : %s.%s,", field, targetName, field)); + } + //Replace last ',' to newline. + builder.setCharAt(builder.length() - 1, '\n'); + builder.append("}"); + resultDes = builder.toString(); + } + return resultDes; + } + + private boolean fillMap(Map<String, ByteIterator> resultMap, Map<String, Object> properties) { + return fillMap(resultMap, properties, null); + } + + /** + * Fills the map with the properties from the BaseDocument. + * + * @param resultMap + * The map to fill/ + * @param obj + * The object to copy values from. + * @return isSuccess + */ + @SuppressWarnings("unchecked") + private boolean fillMap(Map<String, ByteIterator> resultMap, Map<String, Object> properties, Set<String> fields) { + if (fields == null || fields.size() == 0) { + for (Map.Entry<String, Object> entry : properties.entrySet()) { + if (entry.getValue() instanceof String) { + resultMap.put(entry.getKey(), + stringToByteIterator((String)(entry.getValue()))); + } else { + logger.error("Error! Not the format expected! Actually is {}", + entry.getValue().getClass().getName()); + return false; + } + } + } else { + for (String field : fields) { + if (properties.get(field) instanceof String) { + resultMap.put(field, stringToByteIterator((String)(properties.get(field)))); + } else { + logger.error("Error! Not the format expected! Actually is {}", + properties.get(field).getClass().getName()); + return false; + } + } + } + return true; + } + + private String byteIteratorToString(ByteIterator byteIter) { + return new String(byteIter.toArray()); + } + + private ByteIterator stringToByteIterator(String content) { + return new StringByteIterator(content); + } + + private String mapToJson(HashMap<String, ByteIterator> values) { + HashMap<String, String> intervalRst = new HashMap<String, String>(); + for (Map.Entry<String, ByteIterator> entry : values.entrySet()) { + intervalRst.put(entry.getKey(), byteIteratorToString(entry.getValue())); + } + return EntityFactory.toJsonString(intervalRst); + } + +} diff --git a/arangodb/src/main/java/com/yahoo/ycsb/db/package-info.java b/arangodb/src/main/java/com/yahoo/ycsb/db/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..0f3c7e78773f780f08d8c15c8242296c374bacff --- /dev/null +++ b/arangodb/src/main/java/com/yahoo/ycsb/db/package-info.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2012 - 2015 YCSB contributors. All rights reserved. + * + * 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. See accompanying + * LICENSE file. + */ + +/** + * The YCSB binding for <a href="https://www.arangodb.com/">ArangoDB</a>. + */ +package com.yahoo.ycsb.db; + diff --git a/cassandra/pom.xml b/cassandra/pom.xml index c922bbc2678d7753e3ee4b280e1f493d5b34c84c..570e010f1f6da2381df92e13b62643f95197fb8c 100644 --- a/cassandra/pom.xml +++ b/cassandra/pom.xml @@ -63,13 +63,13 @@ LICENSE file. <scope>test</scope> </dependency> <!-- only for Cassandra test (Cassandra 2.2+ uses Sigar for collecting system information, and Sigar requires some native lib files) --> - <dependency> + <dependency> <groupId>org.hyperic</groupId> <artifactId>sigar-dist</artifactId> <version>1.6.4.129</version> <type>zip</type> <scope>test</scope> - </dependency> + </dependency> </dependencies> <profiles> diff --git a/cassandra/src/main/java/com/yahoo/ycsb/db/CassandraCQLClient.java b/cassandra/src/main/java/com/yahoo/ycsb/db/CassandraCQLClient.java old mode 100755 new mode 100644 diff --git a/cassandra/src/test/java/com/yahoo/ycsb/db/CassandraCQLClientTest.java b/cassandra/src/test/java/com/yahoo/ycsb/db/CassandraCQLClientTest.java new file mode 100644 index 0000000000000000000000000000000000000000..60b7e2f33f392d68265f89352b17d62fc261e297 --- /dev/null +++ b/cassandra/src/test/java/com/yahoo/ycsb/db/CassandraCQLClientTest.java @@ -0,0 +1,177 @@ +/** + * Copyright (c) 2015 YCSB contributors All rights reserved. + * + * 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. See accompanying + * LICENSE file. + */ + +package com.yahoo.ycsb.db; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +import com.google.common.collect.Sets; + +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.Row; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.Statement; +import com.datastax.driver.core.querybuilder.Insert; +import com.datastax.driver.core.querybuilder.QueryBuilder; +import com.datastax.driver.core.querybuilder.Select; +import com.yahoo.ycsb.ByteIterator; +import com.yahoo.ycsb.Status; +import com.yahoo.ycsb.StringByteIterator; +import com.yahoo.ycsb.measurements.Measurements; +import com.yahoo.ycsb.workloads.CoreWorkload; + +import org.cassandraunit.CassandraCQLUnit; +import org.cassandraunit.dataset.cql.ClassPathCQLDataSet; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +/** + * Integration tests for the Cassandra client + */ +public class CassandraCQLClientTest { + private final static String TABLE = "usertable"; + private final static String HOST = "localhost"; + private final static int PORT = 9142; + private final static String DEFAULT_ROW_KEY = "user1"; + + private CassandraCQLClient client; + private Session session; + + @ClassRule + public static CassandraCQLUnit cassandraUnit = new CassandraCQLUnit(new ClassPathCQLDataSet("ycsb.cql", "ycsb")); + + @Before + public void setUp() throws Exception { + session = cassandraUnit.getSession(); + + Properties p = new Properties(); + p.setProperty("hosts", HOST); + p.setProperty("port", Integer.toString(PORT)); + p.setProperty("table", TABLE); + + Measurements.setProperties(p); + final CoreWorkload workload = new CoreWorkload(); + workload.init(p); + client = new CassandraCQLClient(); + client.setProperties(p); + client.init(); + } + + @After + public void tearDownClient() throws Exception { + if (client != null) { + client.cleanup(); + } + client = null; + } + + @After + public void clearTable() throws Exception { + // Clear the table so that each test starts fresh. + final Statement truncate = QueryBuilder.truncate(TABLE); + if (cassandraUnit != null) { + cassandraUnit.getSession().execute(truncate); + } + } + + @Test + public void testReadMissingRow() throws Exception { + final HashMap<String, ByteIterator> result = new HashMap<String, ByteIterator>(); + final Status status = client.read(TABLE, "Missing row", null, result); + assertThat(result.size(), is(0)); + assertThat(status, is(Status.NOT_FOUND)); + } + + private void insertRow() { + final String rowKey = DEFAULT_ROW_KEY; + Insert insertStmt = QueryBuilder.insertInto(TABLE); + insertStmt.value(CassandraCQLClient.YCSB_KEY, rowKey); + + insertStmt.value("field0", "value1"); + insertStmt.value("field1", "value2"); + session.execute(insertStmt); + } + + @Test + public void testRead() throws Exception { + insertRow(); + + final HashMap<String, ByteIterator> result = new HashMap<String, ByteIterator>(); + final Status status = client.read(CoreWorkload.table, DEFAULT_ROW_KEY, null, result); + assertThat(status, is(Status.OK)); + assertThat(result.entrySet(), hasSize(11)); + assertThat(result, hasEntry("field2", null)); + + final HashMap<String, String> strResult = new HashMap<String, String>(); + for (final Map.Entry<String, ByteIterator> e : result.entrySet()) { + if (e.getValue() != null) { + strResult.put(e.getKey(), e.getValue().toString()); + } + } + assertThat(strResult, hasEntry(CassandraCQLClient.YCSB_KEY, DEFAULT_ROW_KEY)); + assertThat(strResult, hasEntry("field0", "value1")); + assertThat(strResult, hasEntry("field1", "value2")); + } + + @Test + public void testReadSingleColumn() throws Exception { + insertRow(); + final HashMap<String, ByteIterator> result = new HashMap<String, ByteIterator>(); + final Set<String> fields = Sets.newHashSet("field1"); + final Status status = client.read(CoreWorkload.table, DEFAULT_ROW_KEY, fields, result); + assertThat(status, is(Status.OK)); + assertThat(result.entrySet(), hasSize(1)); + final Map<String, String> strResult = StringByteIterator.getStringMap(result); + assertThat(strResult, hasEntry("field1", "value2")); + } + + @Test + public void testUpdate() throws Exception { + final String key = "key"; + final HashMap<String, String> input = new HashMap<String, String>(); + input.put("field0", "value1"); + input.put("field1", "value2"); + + final Status status = client.insert(TABLE, key, StringByteIterator.getByteIteratorMap(input)); + assertThat(status, is(Status.OK)); + + // Verify result + final Select selectStmt = + QueryBuilder.select("field0", "field1") + .from(TABLE) + .where(QueryBuilder.eq(CassandraCQLClient.YCSB_KEY, key)) + .limit(1); + + final ResultSet rs = session.execute(selectStmt); + final Row row = rs.one(); + assertThat(row, notNullValue()); + assertThat(rs.isExhausted(), is(true)); + assertThat(row.getString("field0"), is("value1")); + assertThat(row.getString("field1"), is("value2")); + } +} diff --git a/cassandra/src/test/resources/ycsb.cql b/cassandra/src/test/resources/ycsb.cql new file mode 100644 index 0000000000000000000000000000000000000000..c52ab787b21a251a5a899b3163e9540e8af0c082 --- /dev/null +++ b/cassandra/src/test/resources/ycsb.cql @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2015 YCSB Contributors. All rights reserved. + * + * 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. See accompanying + * LICENSE file. + */ + +CREATE TABLE usertable ( + y_id varchar primary key, + field0 varchar, + field1 varchar, + field2 varchar, + field3 varchar, + field4 varchar, + field5 varchar, + field6 varchar, + field7 varchar, + field8 varchar, + field9 varchar);