diff --git a/bin/ycsb b/bin/ycsb index 8947cbc5cd8fdbc9b3f3facf1b0edca82f2ad4a4..4db2926e8d043d23947018aa96f5b8e161a145aa 100755 --- a/bin/ycsb +++ b/bin/ycsb @@ -81,6 +81,7 @@ DATABASES = { "mongodb-async": "com.yahoo.ycsb.db.AsyncMongoDbClient", "nosqldb" : "com.yahoo.ycsb.db.NoSqlDbClient", "orientdb" : "com.yahoo.ycsb.db.OrientDBClient", + "rados" : "com.yahoo.ycsb.db.RadosClient", "redis" : "com.yahoo.ycsb.db.RedisClient", "riak" : "com.yahoo.ycsb.db.riak.RiakKVClient", "s3" : "com.yahoo.ycsb.db.S3Client", diff --git a/distribution/pom.xml b/distribution/pom.xml index 67da034bed78d13a14b995441dbb8b69cd3b761d..d3888c9e9daadbe9e8bab3e7812ed759caef1340 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -149,6 +149,11 @@ LICENSE file. <artifactId>orientdb-binding</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>com.yahoo.ycsb</groupId> + <artifactId>rados-binding</artifactId> + <version>${project.version}</version> + </dependency> <dependency> <groupId>com.yahoo.ycsb</groupId> <artifactId>redis-binding</artifactId> diff --git a/pom.xml b/pom.xml index 1519620cafa8f198a5edb2937cd235819a747f29..0a6a072a2e673559d4e788bf3681f8183e028fe9 100644 --- a/pom.xml +++ b/pom.xml @@ -129,6 +129,7 @@ LICENSE file. <module>mongodb</module> <module>nosqldb</module> <module>orientdb</module> + <module>rados</module> <module>redis</module> <module>riak</module> <module>s3</module> diff --git a/rados/README.md b/rados/README.md new file mode 100644 index 0000000000000000000000000000000000000000..826b0189e02fedadd362c5082dc7952e4dd10306 --- /dev/null +++ b/rados/README.md @@ -0,0 +1,72 @@ +<!-- +Copyright (c) 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. +--> + +## Quick Start + +This section describes how to run YCSB on RADOS of Ceph. + +### 1. Start RADOS + +After you start your Ceph cluster, check your cluster’s health first. You can check on the health of your cluster with the following: + + ceph health + +### 2. Install Java and Maven + +### 3. Set Up YCSB + +Git clone YCSB and compile: + + git clone http://github.com/brianfrankcooper/YCSB.git + cd YCSB + mvn clean package + +You can compile only RADOS-binding, EG: + + mvn -pl com.yahoo.ycsb:rados-binding -am clean package + +You can skip the test, EG: + + mvn -pl com.yahoo.ycsb:rados-binding -am clean package -DskipTests + +### 4. Configuration Parameters + +- `rados.configfile` + - The path of the Ceph configuration file + - Default value is '/etc/ceph/ceph.conf' + +- `rados.id` + - The user id to access the RADOS service + - Default value is 'admin' + +- `rados.pool` + - The pool name to be used for benchmark + - Default value is 'data' + +You can set configurations with the shell command, EG: + + ./bin/ycsb load rados -s -P workloads/workloada -p "rados.configfile=/etc/ceph/ceph.conf" -p "rados.id=admin" -p "rados.pool=data" > outputLoad.txt + +### 5. Load data and run tests + +Load the data: + + ./bin/ycsb load rados -s -P workloads/workloada > outputLoad.txt + +Run the workload test: + + ./bin/ycsb run rados -s -P workloads/workloada > outputRun.txt diff --git a/rados/pom.xml b/rados/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..93e5cdb5c24ec4ac6ea6ce698a668a1b3fcc3a98 --- /dev/null +++ b/rados/pom.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Copyright (c) 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.10.0-SNAPSHOT</version> + <relativePath>../binding-parent</relativePath> + </parent> + + <artifactId>rados-binding</artifactId> + <name>rados of Ceph FS binding</name> + <packaging>jar</packaging> + + <dependencies> + <dependency> + <groupId>com.ceph</groupId> + <artifactId>rados</artifactId> + <version>${rados.version}</version> + </dependency> + <dependency> + <groupId>com.yahoo.ycsb</groupId> + <artifactId>core</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.json</groupId> + <artifactId>json</artifactId> + <version>${json.version}</version> + </dependency> + <dependency> + <groupId>net.java.dev.jna</groupId> + <artifactId>jna</artifactId> + <version>4.2.2</version> + </dependency> + + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.12</version> + <scope>test</scope> + </dependency> + </dependencies> + + <properties> + <rados.version>0.2.0</rados.version> + <json.version>20160212</json.version> + </properties> +</project> diff --git a/rados/src/main/java/com/yahoo/ycsb/db/RadosClient.java b/rados/src/main/java/com/yahoo/ycsb/db/RadosClient.java new file mode 100644 index 0000000000000000000000000000000000000000..4e296abc9ac23ec94fa0bfe503d7136810bbc37e --- /dev/null +++ b/rados/src/main/java/com/yahoo/ycsb/db/RadosClient.java @@ -0,0 +1,178 @@ +/** + * Copyright (c) 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. + */ + +package com.yahoo.ycsb.db; + +import com.ceph.rados.Rados; +import com.ceph.rados.IoCTX; +import com.ceph.rados.jna.RadosObjectInfo; +import com.ceph.rados.ReadOp; +import com.ceph.rados.ReadOp.ReadResult; +import com.ceph.rados.exceptions.RadosException; + +import com.yahoo.ycsb.ByteIterator; +import com.yahoo.ycsb.DB; +import com.yahoo.ycsb.DBException; +import com.yahoo.ycsb.Status; +import com.yahoo.ycsb.StringByteIterator; + +import java.io.File; +import java.util.HashMap; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; +import java.util.Vector; + +import org.json.JSONObject; + +/** + * YCSB binding for <a href="http://ceph.org/">RADOS of Ceph</a>. + * + * See {@code rados/README.md} for details. + */ +public class RadosClient extends DB { + + private Rados rados; + private IoCTX ioctx; + + public static final String CONFIG_FILE_PROPERTY = "rados.configfile"; + public static final String CONFIG_FILE_DEFAULT = "/etc/ceph/ceph.conf"; + public static final String ID_PROPERTY = "rados.id"; + public static final String ID_DEFAULT = "admin"; + public static final String POOL_PROPERTY = "rados.pool"; + public static final String POOL_DEFAULT = "data"; + + private boolean isInited = false; + + public void init() throws DBException { + Properties props = getProperties(); + + String configfile = props.getProperty(CONFIG_FILE_PROPERTY); + if (configfile == null) { + configfile = CONFIG_FILE_DEFAULT; + } + + String id = props.getProperty(ID_PROPERTY); + if (id == null) { + id = ID_DEFAULT; + } + + String pool = props.getProperty(POOL_PROPERTY); + if (pool == null) { + pool = POOL_DEFAULT; + } + + // try { + // } catch (UnsatisfiedLinkError e) { + // throw new DBException("RADOS library is not loaded."); + // } + + rados = new Rados(id); + try { + rados.confReadFile(new File(configfile)); + rados.connect(); + ioctx = rados.ioCtxCreate(pool); + } catch (RadosException e) { + throw new DBException(e.getMessage() + ": " + e.getReturnValue()); + } + + isInited = true; + } + + public void cleanup() throws DBException { + if (isInited) { + rados.shutDown(); + rados.ioCtxDestroy(ioctx); + isInited = false; + } + } + + @Override + public Status read(String table, String key, Set<String> fields, HashMap<String, ByteIterator> result) { + byte[] buffer; + + try { + RadosObjectInfo info = ioctx.stat(key); + buffer = new byte[(int)info.getSize()]; + + ReadOp rop = ioctx.readOpCreate(); + ReadResult readResult = rop.queueRead(0, info.getSize()); + // TODO: more size than byte length possible; + // rop.operate(key, Rados.OPERATION_NOFLAG); // for rados-java 0.3.0 + rop.operate(key, 0); + // readResult.raiseExceptionOnError("Error ReadOP(%d)", readResult.getRVal()); // for rados-java 0.3.0 + if (readResult.getRVal() < 0) { + throw new RadosException("Error ReadOP", readResult.getRVal()); + } + if (info.getSize() != readResult.getBytesRead()) { + return new Status("ERROR", "Error the object size read"); + } + readResult.getBuffer().get(buffer); + } catch (RadosException e) { + return new Status("ERROR-" + e.getReturnValue(), e.getMessage()); + } + + JSONObject json = new JSONObject(new String(buffer, java.nio.charset.StandardCharsets.UTF_8)); + Set<String> fieldsToReturn = (fields == null ? json.keySet() : fields); + + for (String name : fieldsToReturn) { + result.put(name, new StringByteIterator(json.getString(name))); + } + + return result.isEmpty() ? Status.ERROR : Status.OK; + } + + @Override + public Status insert(String table, String key, HashMap<String, ByteIterator> values) { + JSONObject json = new JSONObject(); + for (final Entry<String, ByteIterator> e : values.entrySet()) { + json.put(e.getKey(), e.getValue().toString()); + } + + try { + ioctx.write(key, json.toString()); + } catch (RadosException e) { + return new Status("ERROR-" + e.getReturnValue(), e.getMessage()); + } + return Status.OK; + } + + @Override + public Status delete(String table, String key) { + try { + ioctx.remove(key); + } catch (RadosException e) { + return new Status("ERROR-" + e.getReturnValue(), e.getMessage()); + } + return Status.OK; + } + + @Override + public Status update(String table, String key, HashMap<String, ByteIterator> values) { + Status rtn = delete(table, key); + if (rtn.equals(Status.OK)) { + return insert(table, key, values); + } + return rtn; + } + + @Override + public Status scan(String table, String startkey, int recordcount, Set<String> fields, + Vector<HashMap<String, ByteIterator>> result) { + return Status.NOT_IMPLEMENTED; + } +} diff --git a/rados/src/main/java/com/yahoo/ycsb/db/package-info.java b/rados/src/main/java/com/yahoo/ycsb/db/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..e03a0446452b74609fc6ab022a026e572c973987 --- /dev/null +++ b/rados/src/main/java/com/yahoo/ycsb/db/package-info.java @@ -0,0 +1,21 @@ +/** + * Copyright (c) 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. + */ + +/** + * YCSB binding for RADOS of Ceph. + */ +package com.yahoo.ycsb.db; diff --git a/rados/src/test/java/com/yahoo/ycsb/db/RadosClientTest.java b/rados/src/test/java/com/yahoo/ycsb/db/RadosClientTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1501a626a4c2141e132951d660e2409aa7fcd8d8 --- /dev/null +++ b/rados/src/test/java/com/yahoo/ycsb/db/RadosClientTest.java @@ -0,0 +1,150 @@ +/** + * Copyright (c) 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. + */ + +package com.yahoo.ycsb.db; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeNoException; + +import com.yahoo.ycsb.ByteIterator; +import com.yahoo.ycsb.DBException; +import com.yahoo.ycsb.Status; +import com.yahoo.ycsb.StringByteIterator; + +import org.junit.AfterClass; +import org.junit.After; +import org.junit.BeforeClass; +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.UUID; + + + /** + * Test for the binding of <a href="http://ceph.org/">RADOS of Ceph</a>. + * + * See {@code rados/README.md} for details. + */ + +public class RadosClientTest { + + private static RadosClient radosclient; + + public static final String POOL_PROPERTY = "rados.pool"; + public static final String POOL_TEST = "rbd"; + + private static final String TABLE_NAME = "table0"; + private static final String KEY0 = "key0"; + private static final String KEY1 = "key1"; + private static final String KEY2 = "key2"; + private static final HashMap<String, ByteIterator> DATA; + private static final HashMap<String, ByteIterator> DATA_UPDATED; + + static { + DATA = new HashMap<String, ByteIterator>(10); + DATA_UPDATED = new HashMap<String, ByteIterator>(10); + for (int i = 0; i < 10; i++) { + String key = "key" + UUID.randomUUID(); + DATA.put(key, new StringByteIterator("data" + UUID.randomUUID())); + DATA_UPDATED.put(key, new StringByteIterator("data" + UUID.randomUUID())); + } + } + + @BeforeClass + public static void setupClass() throws DBException { + radosclient = new RadosClient(); + + Properties p = new Properties(); + p.setProperty(POOL_PROPERTY, POOL_TEST); + + try { + radosclient.setProperties(p); + radosclient.init(); + } + catch (DBException|UnsatisfiedLinkError e) { + assumeNoException("Ceph cluster is not running. Skipping tests.", e); + } + } + + @AfterClass + public static void teardownClass() throws DBException { + if (radosclient != null) { + radosclient.cleanup(); + } + } + + @Before + public void setUp() { + radosclient.insert(TABLE_NAME, KEY0, DATA); + } + + @After + public void tearDown() { + radosclient.delete(TABLE_NAME, KEY0); + } + + @Test + public void insertTest() { + Status result = radosclient.insert(TABLE_NAME, KEY1, DATA); + assertEquals(Status.OK, result); + } + + @Test + public void updateTest() { + radosclient.insert(TABLE_NAME, KEY2, DATA); + + Status result = radosclient.update(TABLE_NAME, KEY2, DATA_UPDATED); + assertEquals(Status.OK, result); + + HashMap<String, ByteIterator> ret = new HashMap<String, ByteIterator>(10); + radosclient.read(TABLE_NAME, KEY2, DATA.keySet(), ret); + compareMap(DATA_UPDATED, ret); + + radosclient.delete(TABLE_NAME, KEY2); + } + + @Test + public void readTest() { + HashMap<String, ByteIterator> ret = new HashMap<String, ByteIterator>(10); + Status result = radosclient.read(TABLE_NAME, KEY0, DATA.keySet(), ret); + assertEquals(Status.OK, result); + compareMap(DATA, ret); + } + + private void compareMap(HashMap<String, ByteIterator> src, HashMap<String, ByteIterator> dest) { + assertEquals(src.size(), dest.size()); + + Set setSrc = src.entrySet(); + Iterator<Map.Entry> itSrc = setSrc.iterator(); + for (int i = 0; i < 10; i++) { + Map.Entry<String, ByteIterator> entrySrc = itSrc.next(); + assertEquals(entrySrc.getValue().toString(), dest.get(entrySrc.getKey()).toString()); + } + } + + @Test + public void deleteTest() { + Status result = radosclient.delete(TABLE_NAME, KEY0); + assertEquals(Status.OK, result); + } + +}