Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[etcd]: add etcd binding #1674

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bin/bindings.properties
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ dynamodb:site.ycsb.db.DynamoDBClient
elasticsearch:site.ycsb.db.ElasticsearchClient
elasticsearch5:site.ycsb.db.elasticsearch5.ElasticsearchClient
elasticsearch5-rest:site.ycsb.db.elasticsearch5.ElasticsearchRestClient
etcd:site.ycsb.db.etcd.EtcdClient
foundationdb:site.ycsb.db.foundationdb.FoundationDBClient
geode:site.ycsb.db.GeodeClient
googlebigtable:site.ycsb.db.GoogleBigtableClient
Expand Down
1 change: 1 addition & 0 deletions bin/ycsb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ DATABASES = {
"elasticsearch": "site.ycsb.db.ElasticsearchClient",
"elasticsearch5": "site.ycsb.db.elasticsearch5.ElasticsearchClient",
"elasticsearch5-rest": "site.ycsb.db.elasticsearch5.ElasticsearchRestClient",
"etcd": "site.ycsb.db.etcd.EtcdClient",
"foundationdb" : "site.ycsb.db.foundationdb.FoundationDBClient",
"geode" : "site.ycsb.db.GeodeClient",
"googlebigtable" : "site.ycsb.db.GoogleBigtableClient",
Expand Down
5 changes: 5 additions & 0 deletions distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ LICENSE file.
<artifactId>elasticsearch5-binding</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>site.ycsb</groupId>
<artifactId>etcd-binding</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>site.ycsb</groupId>
<artifactId>foundationdb-binding</artifactId>
Expand Down
53 changes: 53 additions & 0 deletions etcd/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<!--
Copyright (c) 2023 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 Redis.

### 1. Start etcd

### 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 -pl site.ycsb:etcd-binding -am clean package -DskipTests

### 4. Provide etcd Connection Parameters and start

First, start etcd server. Then set etcd cluster's endpoints in the workload you plan to run.

- `etcd.endpoints`

Or, you can set configs with the shell command, EG:

./bin/ycsb load etcd -s -P workloads/workloada -p "etcd.endpoints=http://127.0.0.1:2379" > outputLoad.txt

### 5. Load data and run tests

Load the data:

./bin/ycsb load etcd -s -P workloads/workloada -p "etcd.endpoints=http://127.0.0.1:2379" > outputLoad.txt

Run the workload test:

./bin/ycsb run etcd -s -P workloads/workloada -p "etcd.endpoints=http://127.0.0.1:2379" > outputRun.txt

88 changes: 88 additions & 0 deletions etcd/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2023 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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>site.ycsb</groupId>
<artifactId>binding-parent</artifactId>
<version>0.18.0-SNAPSHOT</version>
<relativePath>../binding-parent</relativePath>
</parent>

<artifactId>etcd-binding</artifactId>
<name>etcd DB Binding</name>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>io.etcd</groupId>
<artifactId>jetcd-core</artifactId>
<version>0.6.1</version>
</dependency>
<dependency>
<groupId>site.ycsb</groupId>
<artifactId>core</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-test</artifactId>
<version>4.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.perfmark</groupId>
<artifactId>perfmark-api</artifactId>
<version>0.23.0</version>
</dependency>
<dependency>
<groupId>io.perfmark</groupId>
<artifactId>perfmark-traceviewer</artifactId>
<version>0.23.0</version>
</dependency>
</dependencies>
</project>
200 changes: 200 additions & 0 deletions etcd/src/main/java/site/ycsb/db/etcd/EtcdClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/**
* Copyright (c) 2023 YCSB contributors. All rights reserved.
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.
* <p>
* etcd client binding for YCSB.
* <p>
*/

package site.ycsb.db.etcd;

import io.etcd.jetcd.ByteSequence;
import io.etcd.jetcd.Client;
import io.etcd.jetcd.KeyValue;
import io.etcd.jetcd.options.GetOption;
import io.etcd.jetcd.KV;
import io.etcd.jetcd.kv.GetResponse;
import io.etcd.jetcd.kv.PutResponse;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

import java.time.Duration;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Vector;

import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

import site.ycsb.ByteIterator;
import site.ycsb.DB;
import site.ycsb.DBException;
import site.ycsb.Status;
import site.ycsb.StringByteIterator;


/**
* YCSB binding for <a href="https://etcd.io">etcd</a>.
*
* See {@code etcd/README.md} for details.
*/
public class EtcdClient extends DB {
private Client client;
private KV kvClient;

private static final Charset UTF_8 = StandardCharsets.UTF_8;
private static final String ENDPOINTS = "etcd.endpoints";
private static final String DEFAULT_ENDPOINTS = "http://127.0.0.1:2379";
private static final String SESSION_TIMEOUT_PROPERTY = "etcd.sessionTimeout";
private static final long DEFAULT_SESSION_TIMEOUT = TimeUnit.SECONDS.toMillis(30L);

public void init() throws DBException {
Properties props = getProperties();

String connectString = props.getProperty(ENDPOINTS);
if (connectString == null || connectString.length() == 0) {
connectString = DEFAULT_ENDPOINTS;
}

long sessionTimeout;
String sessionTimeoutString = props.getProperty(SESSION_TIMEOUT_PROPERTY);
if (sessionTimeoutString != null) {
sessionTimeout = Integer.parseInt(sessionTimeoutString);
} else {
sessionTimeout = DEFAULT_SESSION_TIMEOUT;
}

client = Client.builder()
.connectTimeout(Duration.ofMillis(sessionTimeout))
.endpoints(connectString.split(","))
.build();
kvClient = client.getKVClient();
}

@Override
public void cleanup() {
kvClient.close();
client.close();
}

@Override
public Status read(String table, String key, Set<String> fields, Map<String, ByteIterator> result) {
try {
ByteSequence keyByteSequence = ByteSequence.from(key, StandardCharsets.UTF_8);
GetResponse getResponse = kvClient.get(keyByteSequence).get();

if (getResponse.getKvs().isEmpty()) {
return Status.NOT_FOUND;
}

ByteSequence value = getResponse.getKvs().get(0).getValue();
deserializeValues(value.getBytes(), fields, result);
return Status.OK;
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
return Status.ERROR;
}
}

@Override
public Status scan(String table, String startkey, int recordcount, Set<String> fields,
Vector<HashMap<String, ByteIterator>> results) {
try {
ByteSequence startkeyByteSequence = ByteSequence.from(startkey, StandardCharsets.UTF_8);
ByteSequence endkeyByteSequence = ByteSequence.from(new byte[] {0});
GetOption getOption = GetOption.newBuilder()
.withRange(endkeyByteSequence)
.withLimit(recordcount)
.build();
GetResponse getResponse = kvClient.get(startkeyByteSequence, getOption).get();

if (getResponse.getKvs().isEmpty()) {
return Status.NOT_FOUND;
}

List<KeyValue> kvs = getResponse.getKvs();
for (KeyValue kv : kvs) {
ByteSequence value = kv.getValue();
Map<String, ByteIterator> result = new HashMap<>();
deserializeValues(value.getBytes(), fields, result);
results.add(new HashMap<>(result));
}

return Status.OK;
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
return Status.ERROR;
}

}

@Override
public Status update(String table, String key, Map<String, ByteIterator> values) {
return insert(table, key, values);
}

@Override
public Status insert(String table, String key, Map<String, ByteIterator> values) {
try {
ByteSequence keyByteSequence = ByteSequence.from(key, StandardCharsets.UTF_8);
String data = getJsonStrFromByteMap(values);
ByteSequence valueByteSequence = ByteSequence.from(data, StandardCharsets.UTF_8);
PutResponse putResponse = kvClient.put(keyByteSequence, valueByteSequence).get();
return Status.OK;
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
return Status.ERROR;
}
}

@Override
public Status delete(String table, String key) {
try {
ByteSequence keyByteSequence = ByteSequence.from(key, StandardCharsets.UTF_8);
kvClient.delete(keyByteSequence).get();
return Status.OK;
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
return Status.ERROR;
}
}

private Map<String, ByteIterator> deserializeValues(final byte[] data, final Set<String> fields,
final Map<String, ByteIterator> result) {
JSONObject jsonObject = (JSONObject) JSONValue.parse(new String(data, UTF_8));
Iterator<String> iterator = jsonObject.keySet().iterator();
while(iterator.hasNext()) {
String field = iterator.next();
String value = jsonObject.get(field).toString();
if(fields == null || fields.contains(field)) {
result.put(field, new StringByteIterator(value));
}
}
return result;
}

private static String getJsonStrFromByteMap(Map<String, ByteIterator> map) {
Map<String, String> stringMap = StringByteIterator.getStringMap(map);
return JSONValue.toJSONString(stringMap);
}
}
22 changes: 22 additions & 0 deletions etcd/src/main/java/site/ycsb/db/etcd/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2023 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://etcd.io/">etcd</a>.
*/
package site.ycsb.db.etcd;

Loading