From ef8c9f1667157bac158bc7cf5b2676367d938229 Mon Sep 17 00:00:00 2001
From: Shivam Maharshi <shivam.maharshi@gmail.com>
Date: Sat, 28 May 2016 12:49:53 -0400
Subject: [PATCH] [rest] Added rest-binding for benchmarking RESTFull Web
 Services.

---
 bin/bindings.properties                       |   1 +
 bin/ycsb                                      |   1 +
 checkstyle.xml                                |   4 +-
 .../yahoo/ycsb/workloads/RestWorkload.java    | 304 ++++++++++++++
 distribution/pom.xml                          |   5 +
 pom.xml                                       |   1 +
 rest/README.md                                | 181 +++++++++
 rest/pom.xml                                  | 115 ++++++
 .../ycsb/webservice/rest/RestClient.java      | 370 ++++++++++++++++++
 .../ycsb/webservice/rest/package-info.java    |  22 ++
 .../ycsb/webservice/rest/IntegrationTest.java | 246 ++++++++++++
 .../ycsb/webservice/rest/ResourceLoader.java  |  38 ++
 .../ycsb/webservice/rest/RestClientTest.java  | 226 +++++++++++
 .../webservice/rest/RestTestResource.java     |  71 ++++
 .../com/yahoo/ycsb/webservice/rest/Utils.java |  88 +++++
 rest/src/test/resources/WebContent/index.html |  13 +
 rest/src/test/resources/error_trace.txt       |   1 +
 rest/src/test/resources/trace.txt             |   5 +
 rest/src/test/resources/workload_rest         |  68 ++++
 19 files changed, 1759 insertions(+), 1 deletion(-)
 create mode 100644 core/src/main/java/com/yahoo/ycsb/workloads/RestWorkload.java
 create mode 100644 rest/README.md
 create mode 100644 rest/pom.xml
 create mode 100644 rest/src/main/java/com/yahoo/ycsb/webservice/rest/RestClient.java
 create mode 100644 rest/src/main/java/com/yahoo/ycsb/webservice/rest/package-info.java
 create mode 100644 rest/src/test/java/com/yahoo/ycsb/webservice/rest/IntegrationTest.java
 create mode 100644 rest/src/test/java/com/yahoo/ycsb/webservice/rest/ResourceLoader.java
 create mode 100644 rest/src/test/java/com/yahoo/ycsb/webservice/rest/RestClientTest.java
 create mode 100644 rest/src/test/java/com/yahoo/ycsb/webservice/rest/RestTestResource.java
 create mode 100644 rest/src/test/java/com/yahoo/ycsb/webservice/rest/Utils.java
 create mode 100644 rest/src/test/resources/WebContent/index.html
 create mode 100644 rest/src/test/resources/error_trace.txt
 create mode 100644 rest/src/test/resources/trace.txt
 create mode 100644 rest/src/test/resources/workload_rest

diff --git a/bin/bindings.properties b/bin/bindings.properties
index d75730c1..580ae742 100644
--- a/bin/bindings.properties
+++ b/bin/bindings.properties
@@ -56,6 +56,7 @@ nosqldb:com.yahoo.ycsb.db.NoSqlDbClient
 orientdb:com.yahoo.ycsb.db.OrientDBClient
 rados:com.yahoo.ycsb.db.RadosClient
 redis:com.yahoo.ycsb.db.RedisClient
+rest:com.yahoo.ycsb.webservice.rest.RestClient
 riak:com.yahoo.ycsb.db.riak.RiakKVClient
 s3:com.yahoo.ycsb.db.S3Client
 solr:com.yahoo.ycsb.db.solr.SolrClient
diff --git a/bin/ycsb b/bin/ycsb
index e0f7092e..72e218a5 100755
--- a/bin/ycsb
+++ b/bin/ycsb
@@ -81,6 +81,7 @@ DATABASES = {
     "orientdb"     : "com.yahoo.ycsb.db.OrientDBClient",
     "rados"        : "com.yahoo.ycsb.db.RadosClient",
     "redis"        : "com.yahoo.ycsb.db.RedisClient",
+    "rest"         : "com.yahoo.ycsb.webservice.rest.RestClient",
     "riak"         : "com.yahoo.ycsb.db.riak.RiakKVClient",
     "s3"           : "com.yahoo.ycsb.db.S3Client",
     "solr"         : "com.yahoo.ycsb.db.solr.SolrClient",
diff --git a/checkstyle.xml b/checkstyle.xml
index 92d1ce37..af0065d0 100644
--- a/checkstyle.xml
+++ b/checkstyle.xml
@@ -144,7 +144,9 @@ LICENSE file.
         <!-- Checks for blocks. You know, those {}'s         -->
         <!-- See http://checkstyle.sf.net/config_blocks.html -->
         <module name="AvoidNestedBlocks"/>
-        <module name="EmptyBlock"/>
+        <module name="EmptyBlock">
+          <property name="option" value="text"/>
+        </module>
         <module name="LeftCurly"/>
         <module name="NeedBraces"/>
         <module name="RightCurly"/>
diff --git a/core/src/main/java/com/yahoo/ycsb/workloads/RestWorkload.java b/core/src/main/java/com/yahoo/ycsb/workloads/RestWorkload.java
new file mode 100644
index 00000000..b36c65a9
--- /dev/null
+++ b/core/src/main/java/com/yahoo/ycsb/workloads/RestWorkload.java
@@ -0,0 +1,304 @@
+/**
+ * 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.workloads;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import com.yahoo.ycsb.ByteIterator;
+import com.yahoo.ycsb.DB;
+import com.yahoo.ycsb.RandomByteIterator;
+import com.yahoo.ycsb.WorkloadException;
+import com.yahoo.ycsb.generator.DiscreteGenerator;
+import com.yahoo.ycsb.generator.ExponentialGenerator;
+import com.yahoo.ycsb.generator.HotspotIntegerGenerator;
+import com.yahoo.ycsb.generator.NumberGenerator;
+import com.yahoo.ycsb.generator.UniformIntegerGenerator;
+import com.yahoo.ycsb.generator.ZipfianGenerator;
+
+/**
+ * Typical RESTFul services benchmarking scenario. Represents a set of client
+ * calling REST operations like HTTP DELETE, GET, POST, PUT on a web service.
+ * This scenario is completely different from CoreWorkload which is mainly
+ * designed for databases benchmarking. However due to some reusable
+ * functionality this class extends {@link CoreWorkload} and overrides necessary
+ * methods like init, doTransaction etc.
+ */
+public class RestWorkload extends CoreWorkload {
+
+  /**
+   * The name of the property for the proportion of transactions that are
+   * delete.
+   */
+  public static final String DELETE_PROPORTION_PROPERTY = "deleteproportion";
+
+  /**
+   * The default proportion of transactions that are delete.
+   */
+  public static final String DELETE_PROPORTION_PROPERTY_DEFAULT = "0.00";
+
+  /**
+   * The name of the property for the file that holds the field length size for insert operations.
+   */
+  public static final String FIELD_LENGTH_DISTRIBUTION_FILE_PROPERTY = "fieldlengthdistfile";
+  
+  /**
+   * The default file name that holds the field length size for insert operations.
+   */
+  public static final String FIELD_LENGTH_DISTRIBUTION_FILE_PROPERTY_DEFAULT = "fieldLengthDistFile.txt";
+
+  /**
+   * In web services even though the CRUD operations follow the same request
+   * distribution, they have different traces and distribution parameter
+   * values. Hence configuring the parameters of these operations separately
+   * makes the benchmark more flexible and capable of generating better
+   * realistic workloads.
+   */
+  // Read related properties.
+  private static final String READ_TRACE_FILE = "url.trace.read";
+  private static final String READ_TRACE_FILE_DEFAULT = "readtrace.txt";
+  private static final String READ_ZIPFIAN_CONSTANT = "readzipfconstant";
+  private static final String READ_ZIPFIAN_CONSTANT_DEAFULT = "0.99";
+  private static final String READ_RECORD_COUNT_PROPERTY = "readrecordcount";
+  // Insert related properties.
+  private static final String INSERT_TRACE_FILE = "url.trace.insert";
+  private static final String INSERT_TRACE_FILE_DEFAULT = "inserttrace.txt";
+  private static final String INSERT_ZIPFIAN_CONSTANT = "insertzipfconstant";
+  private static final String INSERT_ZIPFIAN_CONSTANT_DEAFULT = "0.99";
+  private static final String INSERT_SIZE_ZIPFIAN_CONSTANT = "insertsizezipfconstant";
+  private static final String INSERT_SIZE_ZIPFIAN_CONSTANT_DEAFULT = "0.99";
+  private static final String INSERT_RECORD_COUNT_PROPERTY = "insertrecordcount";
+  // Delete related properties.
+  private static final String DELETE_TRACE_FILE = "url.trace.delete";
+  private static final String DELETE_TRACE_FILE_DEFAULT = "deletetrace.txt";
+  private static final String DELETE_ZIPFIAN_CONSTANT = "deletezipfconstant";
+  private static final String DELETE_ZIPFIAN_CONSTANT_DEAFULT = "0.99";
+  private static final String DELETE_RECORD_COUNT_PROPERTY = "deleterecordcount";
+  // Delete related properties.
+  private static final String UPDATE_TRACE_FILE = "url.trace.update";
+  private static final String UPDATE_TRACE_FILE_DEFAULT = "updatetrace.txt";
+  private static final String UPDATE_ZIPFIAN_CONSTANT = "updatezipfconstant";
+  private static final String UPDATE_ZIPFIAN_CONSTANT_DEAFULT = "0.99";
+  private static final String UPDATE_RECORD_COUNT_PROPERTY = "updaterecordcount";
+
+  private Map<Integer, String> readUrlMap;
+  private Map<Integer, String> insertUrlMap;
+  private Map<Integer, String> deleteUrlMap;
+  private Map<Integer, String> updateUrlMap;
+  private int readRecordCount;
+  private int insertRecordCount;
+  private int deleteRecordCount;
+  private int updateRecordCount;
+  private NumberGenerator readKeyChooser;
+  private NumberGenerator insertKeyChooser;
+  private NumberGenerator deleteKeyChooser;
+  private NumberGenerator updateKeyChooser;
+
+  @Override
+  public void init(Properties p) throws WorkloadException {
+
+    readRecordCount = Integer.parseInt(p.getProperty(READ_RECORD_COUNT_PROPERTY, String.valueOf(Integer.MAX_VALUE)));
+    insertRecordCount = Integer
+        .parseInt(p.getProperty(INSERT_RECORD_COUNT_PROPERTY, String.valueOf(Integer.MAX_VALUE)));
+    deleteRecordCount = Integer
+        .parseInt(p.getProperty(DELETE_RECORD_COUNT_PROPERTY, String.valueOf(Integer.MAX_VALUE)));
+    updateRecordCount = Integer
+        .parseInt(p.getProperty(UPDATE_RECORD_COUNT_PROPERTY, String.valueOf(Integer.MAX_VALUE)));
+      
+      readUrlMap = getTrace(p.getProperty(READ_TRACE_FILE, READ_TRACE_FILE_DEFAULT), readRecordCount);
+      insertUrlMap = getTrace(p.getProperty(INSERT_TRACE_FILE, INSERT_TRACE_FILE_DEFAULT), insertRecordCount);
+      deleteUrlMap = getTrace(p.getProperty(DELETE_TRACE_FILE, DELETE_TRACE_FILE_DEFAULT), deleteRecordCount);
+      updateUrlMap = getTrace(p.getProperty(UPDATE_TRACE_FILE, UPDATE_TRACE_FILE_DEFAULT), updateRecordCount);
+    
+    operationchooser = createOperationGenerator(p);
+
+    // Common distribution for all operations.
+    String requestDistrib = p.getProperty(REQUEST_DISTRIBUTION_PROPERTY, REQUEST_DISTRIBUTION_PROPERTY_DEFAULT);
+
+    double readZipfconstant = Double
+        .parseDouble(p.getProperty(READ_ZIPFIAN_CONSTANT, READ_ZIPFIAN_CONSTANT_DEAFULT));
+    readKeyChooser = getKeyChooser(requestDistrib, readUrlMap.size(), readZipfconstant, p);
+    double updateZipfconstant = Double
+        .parseDouble(p.getProperty(UPDATE_ZIPFIAN_CONSTANT, UPDATE_ZIPFIAN_CONSTANT_DEAFULT));
+    updateKeyChooser = getKeyChooser(requestDistrib, updateUrlMap.size(), updateZipfconstant, p);
+    double insertZipfconstant = Double
+        .parseDouble(p.getProperty(INSERT_ZIPFIAN_CONSTANT, INSERT_ZIPFIAN_CONSTANT_DEAFULT));
+    insertKeyChooser = getKeyChooser(requestDistrib, insertUrlMap.size(), insertZipfconstant, p);
+    double deleteZipfconstant = Double
+        .parseDouble(p.getProperty(DELETE_ZIPFIAN_CONSTANT, DELETE_ZIPFIAN_CONSTANT_DEAFULT));
+    deleteKeyChooser = getKeyChooser(requestDistrib, deleteUrlMap.size(), deleteZipfconstant, p);
+
+    fieldlengthgenerator = getFieldLengthGenerator(p);
+  }
+  
+  public static DiscreteGenerator createOperationGenerator(final Properties p) {
+     // Re-using CoreWorkload method. 
+    final DiscreteGenerator operationChooser = CoreWorkload.createOperationGenerator(p);
+    // Needs special handling for delete operations not supported in CoreWorkload.
+    double deleteproportion = Double
+		        .parseDouble(p.getProperty(DELETE_PROPORTION_PROPERTY, DELETE_PROPORTION_PROPERTY_DEFAULT));
+    if (deleteproportion > 0)
+      operationChooser.addValue(deleteproportion, "DELETE");
+    return operationChooser;
+  }
+
+  private static NumberGenerator getKeyChooser(String requestDistrib, int recordCount, double zipfContant,
+      Properties p) throws WorkloadException {
+    NumberGenerator keychooser = null;
+    
+    switch(requestDistrib) {
+      case "exponential":
+        double percentile = Double.parseDouble(p.getProperty(ExponentialGenerator.EXPONENTIAL_PERCENTILE_PROPERTY,
+            ExponentialGenerator.EXPONENTIAL_PERCENTILE_DEFAULT));
+        double frac = Double.parseDouble(p.getProperty(ExponentialGenerator.EXPONENTIAL_FRAC_PROPERTY,
+            ExponentialGenerator.EXPONENTIAL_FRAC_DEFAULT));
+        keychooser = new ExponentialGenerator(percentile, recordCount * frac);
+        break;
+      case "uniform":
+        keychooser = new UniformIntegerGenerator(0, recordCount - 1);
+        break;
+      case "zipfian":
+        keychooser = new ZipfianGenerator(recordCount, zipfContant);
+        break;
+      case "latest":
+        throw new WorkloadException("Latest request distribution is not supported for RestWorkload.");
+      case "hotspot":
+        double hotsetfraction = Double
+          .parseDouble(p.getProperty(HOTSPOT_DATA_FRACTION, HOTSPOT_DATA_FRACTION_DEFAULT));
+        double hotopnfraction = Double
+          .parseDouble(p.getProperty(HOTSPOT_OPN_FRACTION, HOTSPOT_OPN_FRACTION_DEFAULT));
+        keychooser = new HotspotIntegerGenerator(0, recordCount - 1, hotsetfraction, hotopnfraction);
+        break;
+      default:
+        throw new WorkloadException("Unknown request distribution \"" + requestDistrib + "\"");
+    }
+    return keychooser;
+  }
+
+  protected static NumberGenerator getFieldLengthGenerator(Properties p) throws WorkloadException {
+    // Re-using CoreWorkload method. 
+    NumberGenerator fieldLengthGenerator = CoreWorkload.getFieldLengthGenerator(p);
+    String fieldlengthdistribution = p.getProperty(
+            FIELD_LENGTH_DISTRIBUTION_PROPERTY, FIELD_LENGTH_DISTRIBUTION_PROPERTY_DEFAULT);
+    // Needs special handling for Zipfian distribution for variable Zipf Constant.
+    if (fieldlengthdistribution.compareTo("zipfian") == 0) {
+    	  int fieldlength =
+    		        Integer.parseInt(p.getProperty(FIELD_LENGTH_PROPERTY, FIELD_LENGTH_PROPERTY_DEFAULT));
+        double insertsizezipfconstant = Double
+            .parseDouble(p.getProperty(INSERT_SIZE_ZIPFIAN_CONSTANT, INSERT_SIZE_ZIPFIAN_CONSTANT_DEAFULT));
+        fieldLengthGenerator = new ZipfianGenerator(1, fieldlength, insertsizezipfconstant);
+      }
+    return fieldLengthGenerator;
+  }
+
+  /**
+   *  Reads the trace file and returns a URL map.
+   */
+  private static Map<Integer, String> getTrace(String filePath, int recordCount)
+      throws WorkloadException {
+    Map<Integer, String> urlMap = new HashMap<Integer, String>();
+    int count = 0;
+    String line;
+    try {
+      FileReader inputFile = new FileReader(filePath);
+      BufferedReader bufferReader = new BufferedReader(inputFile);
+      while ((line = bufferReader.readLine()) != null) {
+        urlMap.put(count++, line.trim());
+        if (count >= recordCount)
+          break;
+      }
+      bufferReader.close();
+    } catch (IOException e) {
+      throw new WorkloadException(
+          "Error while reading the trace. Please make sure the trace file path is correct. "
+              + e.getLocalizedMessage());
+    }
+    return urlMap;
+  }
+
+  /**
+   * Not required for Rest Clients as data population is service specific.
+   */
+  @Override
+  public boolean doInsert(DB db, Object threadstate) {
+    return false;
+  }
+
+  @Override
+  public boolean doTransaction(DB db, Object threadstate) {
+    switch (operationchooser.nextString()) {
+    case "UPDATE":
+      doTransactionUpdate(db);
+      break;
+    case "INSERT":
+      doTransactionInsert(db);
+      break;
+    case "DELETE":
+      doTransactionDelete(db);
+      break;
+    default:
+      doTransactionRead(db);
+    }
+    return true;
+  }
+  
+  /**
+   *  Returns next URL to be called.
+   */
+  private String getNextURL(int opType) {
+    if (opType == 1)
+      return readUrlMap.get(readKeyChooser.nextValue().intValue());
+    else if (opType == 2)
+      return insertUrlMap.get(insertKeyChooser.nextValue().intValue());
+    else if (opType == 3)
+      return deleteUrlMap.get(deleteKeyChooser.nextValue().intValue());
+    else
+      return updateUrlMap.get(updateKeyChooser.nextValue().intValue());
+  }
+
+  @Override
+  public void doTransactionRead(DB db) {
+    HashMap<String, ByteIterator> result = new HashMap<String, ByteIterator>();
+    db.read(null, getNextURL(1), null, result);
+  } 
+  
+  @Override
+  public void doTransactionInsert(DB db) {
+    HashMap<String, ByteIterator> value = new HashMap<String, ByteIterator>();
+    // Create random bytes of insert data with a specific size.
+    value.put("data", new RandomByteIterator(fieldlengthgenerator.nextValue().longValue()));
+    db.insert(null, getNextURL(2), value);
+  }
+
+  public void doTransactionDelete(DB db) {
+    db.delete(null, getNextURL(3));
+  }
+
+  @Override
+  public void doTransactionUpdate(DB db) {
+    HashMap<String, ByteIterator> value = new HashMap<String, ByteIterator>();
+    // Create random bytes of update data with a specific size.
+    value.put("data", new RandomByteIterator(fieldlengthgenerator.nextValue().longValue()));
+    db.update(null, getNextURL(4), value);
+  }
+  
+}
diff --git a/distribution/pom.xml b/distribution/pom.xml
index ff3b1116..db690ffc 100644
--- a/distribution/pom.xml
+++ b/distribution/pom.xml
@@ -164,6 +164,11 @@ LICENSE file.
       <artifactId>redis-binding</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>com.yahoo.ycsb</groupId>
+      <artifactId>rest-binding</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>com.yahoo.ycsb</groupId>
       <artifactId>riak-binding</artifactId>
diff --git a/pom.xml b/pom.xml
index 510dace4..585bf584 100644
--- a/pom.xml
+++ b/pom.xml
@@ -133,6 +133,7 @@ LICENSE file.
     <module>orientdb</module>
     <module>rados</module>
     <module>redis</module>
+    <module>rest</module>
     <module>riak</module>
     <module>s3</module>
     <module>solr</module>
diff --git a/rest/README.md b/rest/README.md
new file mode 100644
index 00000000..ffc9a1b8
--- /dev/null
+++ b/rest/README.md
@@ -0,0 +1,181 @@
+<!--
+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 to benchmark HTTP RESTful
+webservices. The aim of the rest binding is to benchmark the 
+performance of any sepecific HTTP RESTful webservices with real
+life (production) dataset. This must not be confused with benchmarking
+various webservers (like Apache Tomcat, Nginx, Jetty) using a dummy 
+dataset.
+
+### 1. Set Up YCSB
+
+Clone the YCSB git repository and compile:
+
+    git clone git://github.com/brianfrankcooper/YCSB.git
+    cd YCSB
+    mvn -pl com.yahoo.ycsb:rest-binding -am clean package
+
+### 2. Set Up an HTTP Web Service
+
+There must be a running HTTP RESTful webservice accesible from 
+the instance on which YCSB is running. If the webservice is 
+running on the local instance default HTTP port 80, it's base
+URL will look like http://127.0.0.1:80/{service_endpoint}. The
+rest binding assumes that the webservice to be benchmarked already
+has a valid dataset. THe rest module has been designed in this 
+way for two reasons:
+
+1. The performance of most webservices depends on the size, pattern
+and the nature of the real life dataset accesible from these services.
+Hence creating a dummy dataset might not actually reflect the true
+performance of a webservice to be benchmarked.
+
+2. Since many webservices have a non-naive backend which includes
+interaction with multiple backend components, tables and databases.
+Generating a dummy dataset for such webservices is a non-trivial and
+a time consuming task.
+
+However to benchmark a webservice before it has access to a real 
+dataset, support for automatic data insertion can be added in the
+future. An example of such a scenario is benchmarking a webservice
+before it moves to production.
+
+### 3. Run YCSB
+    
+At this point we assume that you've setup a webservice accesible at
+an HTTP endpoint like this: http://{host}:{port}/{service_endpoint}.
+
+Before you are ready to run please ensure that you have prepared a
+trace for the CRUD operations to benchmark your webservice. 
+
+Trace is a collection of URL resources that should be hit in order
+to benchmark any webservice. The more realistic this collection of
+URL is, the more reliable and accurate are the benchmarking results
+because this means simulating the real life workload more accurately.
+Tracefile is a file that holds the trace. For example, if your 
+webservice exists at http://{host}:{port}/{endpoint}, and you want
+to benchmark the performance of READS on this webservice with five
+resources (namely resource_1, resource_2 ... resource_5) then the
+url.trace.read file will look like this:
+
+http://{host}:{port}/{endpoint}/resource_1
+http://{host}:{port}/{endpoint}/resource_2
+http://{host}:{port}/{endpoint}/resource_3
+http://{host}:{port}/{endpoint}/resource_4
+http://{host}:{port}/{endpoint}/resource_5
+
+The rest module will pick up URLs from the above file according to
+the `requestdistribution` property (default is zipfian) mentioned in
+the rest_workload. In the example above we assume that the property 
+`url.prefix` (see below for property description) is set to empty. If
+url.prefix property is set to `http://{host}:{port}/{endpoint}/` the 
+equivalent of the read trace given above would look like:
+
+resource_1
+resource_2
+resource_3
+resource_4
+resource_5
+
+In real life the traces for various CRUD operations are diffent
+from one another. HTTP GET will rarely have the same URL access
+pattern as that of HTTP POST or HTTP PUT. Hence to give enough
+flexibility to benchmark webservices, different trace files can
+be used for different CRUD operations. However if you wish to use
+the same trace for all these operations, just pass the same file
+to all these properties - `url.trace.read`, `url.trace.insert`,
+`url.trace.update` & `url.trace.delete`.
+
+Now you are ready to run! Run the rest_workload:
+
+    ./bin/ycsb run rest -s -P workloads/rest_workload
+
+For further configuration see below: 
+
+### Default Configuration Parameters
+The default settings for the rest binding are as follows:
+
+- `url.prefix` 
+  - The base endpoint URL where the webservice is running. URLs from trace files (DELETE, GET, POST, PUT) will be prefixed with this value before making an HTTP request. A common usage value would be http://127.0.0.1:8080/{yourService}
+  - Default value is `http://127.0.0.1:80/`.
+  
+- `url.trace.read` 
+  - The path to a trace file that holds the URLs to be invoked for HTTP GET method. URLs must be seperated by a newline.
+  
+- `url.trace.insert` 
+  - The path to a trace file that holds the URLs to be invoked for HTTP POST method. URLs must be seperated by a newline. 
+
+- `url.trace.update` 
+  - The path to a trace file that holds the URLs to be invoked for HTTP PUT method. URLs must be seperated by a newline.
+
+- `url.trace.delete` 
+  - The path to a trace file that holds the URLs to be invoked for HTTP DELETE method. URLs must be seperated by a newline.
+
+- `headers` 
+  - The HTTP request headers used for all requests. Headers must be separated by space as a delimiter.
+  - Default value is `Accept */* Accept-Language en-US,en;q=0.5 Content-Type application/x-www-form-urlencoded user-agent Mozilla/5.0`
+
+- `timeout.con` 
+  - The HTTP connection timeout in seconds. The response will be considered as an error if the client fails to connect with the server within this time limit.
+  - Default value is `10` seconds.
+
+- `timeout.read` 
+  - The HTTP read timeout in seconds. The response will be considered as an error if the client fails to read from the server within this time limit.
+  - Default value is `10` seconds.
+  
+- `timeout.exec` 
+  - The time within which request must return a response. The response will be considered as an error if the client fails to complete the request within this time limit.
+  - Default value is `10` seconds.
+
+- `log.enable` 
+  - A Boolean value to enable console status logs. When true, it will print all the HTTP requests being made and thier response status on the YCSB console window.
+  - Default value is `false`.
+
+- `readrecordcount` 
+  - An integer value that signifies the top k URLs (entries) to be picked from the `url.trace.read` file for making HTTP GET requests. Must have a value greater than 0. If this value exceeds the number of entries present in `url.trace.read` file, then k will be set to the number of entries in the file.
+  - Default value is `10000`. 
+
+- `insertrecordcount` 
+  - An integer value that signifies the top k URLs to be picked from the `url.trace.insert` file for making HTTP POST requests. Must have a value greater than 0. If this value exceeds the number of entries present in `url.trace.insert` file, then k will be set to the number of entries in the file.
+  - Default value is `5000`. 
+
+- `deleterecordcount` 
+  - An integer value that signifies the top k URLs to be picked from the `url.trace.delete` file for making HTTP DELETE requests. Must have a value greater than 0. If this value exceeds the number of entries present in `url.trace.delete` file, then k will be set to the number of entries in the file.
+  - Default value is `1000`.
+
+- `updaterecordcount` 
+  - An integer value that signifies the top k URLs to be picked from the `url.trace.update` file for making HTTP PUT requests. Must have a value greater than 0. If this value exceeds the number of entries present in `url.trace.update` file, then k will be set to the number of entries in the file.
+  - Default value is `1000`.
+
+- `readzipfconstant` 
+  - An double value of the Zipf's constant to be used for insert requests. Applicable only if the requestdistribution = `zipfian`.
+  - Default value is `0.9`.
+
+- `insertzipfconstant` 
+  - An double value of the Zipf's constant to be used for insert requests. Applicable only if the requestdistribution = `zipfian`. 
+  - Default value is `0.9`.
+  
+- `updatezipfconstant` 
+  - An double value of the Zipf's constant to be used for insert requests. Applicable only if the requestdistribution = `zipfian`. 
+  - Default value is `0.9`.
+
+- `deletezipfconstant` 
+  - An double value of the Zipf's constant to be used for insert requests. Applicable only if the requestdistribution = `zipfian`. 
+  - Default value is `0.9`.
diff --git a/rest/pom.xml b/rest/pom.xml
new file mode 100644
index 00000000..2fec0059
--- /dev/null
+++ b/rest/pom.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (c) 2011 YCSB project, 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>rest-binding</artifactId>
+  <name>Rest Client Binding</name>
+  <packaging>jar</packaging>
+
+  <properties>
+    <tomcat.version>8.0.28</tomcat.version>
+    <jersey.version>2.6</jersey.version>
+    <httpclient.version>4.5.1</httpclient.version>
+    <httpcore.version>4.4.4</httpcore.version>
+    <junit.version>4.12</junit.version>
+    <system-rules.version>1.16.0</system-rules.version>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.yahoo.ycsb</groupId>
+      <artifactId>core</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpclient</artifactId>
+      <version>${httpclient.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpcore</artifactId>
+      <version>${httpcore.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>${junit.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.github.stefanbirkner</groupId>
+      <artifactId>system-rules</artifactId>
+      <version>${system-rules.version}</version>
+    </dependency>
+  	<dependency>
+      <groupId>org.glassfish.jersey.core</groupId>
+      <artifactId>jersey-server</artifactId>
+      <version>${jersey.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.glassfish.jersey.core</groupId>
+      <artifactId>jersey-client</artifactId>
+      <version>${jersey.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.glassfish.jersey.containers</groupId>
+      <artifactId>jersey-container-servlet-core</artifactId>
+      <version>${jersey.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.tomcat</groupId>
+      <artifactId>tomcat-dbcp</artifactId>
+      <version>${tomcat.version}</version>
+      <exclusions>
+        <exclusion>
+          <groupId>org.apache.tomcat</groupId>
+          <artifactId>tomcat-juli</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.tomcat.embed</groupId>
+      <artifactId>tomcat-embed-core</artifactId>
+      <version>${tomcat.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.tomcat.embed</groupId>
+      <artifactId>tomcat-embed-logging-juli</artifactId>
+      <version>${tomcat.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.tomcat.embed</groupId>
+      <artifactId>tomcat-embed-logging-log4j</artifactId>
+      <version>${tomcat.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.tomcat.embed</groupId>
+      <artifactId>tomcat-embed-jasper</artifactId>
+      <version>${tomcat.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.tomcat.embed</groupId>
+      <artifactId>tomcat-embed-websocket</artifactId>
+      <version>${tomcat.version}</version>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/rest/src/main/java/com/yahoo/ycsb/webservice/rest/RestClient.java b/rest/src/main/java/com/yahoo/ycsb/webservice/rest/RestClient.java
new file mode 100644
index 00000000..2fd14673
--- /dev/null
+++ b/rest/src/main/java/com/yahoo/ycsb/webservice/rest/RestClient.java
@@ -0,0 +1,370 @@
+/**
+ * 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.webservice.rest;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.Properties;
+import java.util.Set;
+import java.util.Vector;
+import java.util.zip.GZIPInputStream;
+
+import javax.ws.rs.HttpMethod;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.util.EntityUtils;
+
+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;
+
+/**
+ * Class responsible for making web service requests for benchmarking purpose.
+ * Using Apache HttpClient over standard Java HTTP API as this is more flexible
+ * and provides better functionality. For example HttpClient can automatically
+ * handle redirects and proxy authentication which the standard Java API can't.
+ */
+public class RestClient extends DB {
+
+  private static final String URL_PREFIX = "url.prefix";
+  private static final String CON_TIMEOUT = "timeout.con";
+  private static final String READ_TIMEOUT = "timeout.read";
+  private static final String EXEC_TIMEOUT = "timeout.exec";
+  private static final String LOG_ENABLED = "log.enable";
+  private static final String HEADERS = "headers";
+  private static final String COMPRESSED_RESPONSE = "response.compression";
+  private boolean compressedResponse;
+  private boolean logEnabled;
+  private String urlPrefix;
+  private Properties props;
+  private String[] headers;
+  private CloseableHttpClient client;
+  private int conTimeout = 10000;
+  private int readTimeout = 10000;
+  private int execTimeout = 10000;
+  private volatile Criteria requestTimedout = new Criteria(false);
+
+  @Override
+  public void init() throws DBException {
+    props = getProperties();
+    urlPrefix = props.getProperty(URL_PREFIX, "http://127.0.0.1:8080");
+    conTimeout = Integer.valueOf(props.getProperty(CON_TIMEOUT, "10")) * 1000;
+    readTimeout = Integer.valueOf(props.getProperty(READ_TIMEOUT, "10")) * 1000;
+    execTimeout = Integer.valueOf(props.getProperty(EXEC_TIMEOUT, "10")) * 1000;
+    logEnabled = Boolean.valueOf(props.getProperty(LOG_ENABLED, "false").trim());
+    compressedResponse = Boolean.valueOf(props.getProperty(COMPRESSED_RESPONSE, "false").trim());
+    headers = props.getProperty(HEADERS, "Accept */* Content-Type application/xml user-agent Mozilla/5.0 ").trim()
+          .split(" ");
+    setupClient();
+  }
+
+  private void setupClient() {
+    RequestConfig.Builder requestBuilder = RequestConfig.custom();
+    requestBuilder = requestBuilder.setConnectTimeout(conTimeout);
+    requestBuilder = requestBuilder.setConnectionRequestTimeout(readTimeout);
+    requestBuilder = requestBuilder.setSocketTimeout(readTimeout);
+    HttpClientBuilder clientBuilder = HttpClientBuilder.create().setDefaultRequestConfig(requestBuilder.build());
+    this.client = clientBuilder.setConnectionManagerShared(true).build();
+  }
+
+  @Override
+  public Status read(String table, String endpoint, Set<String> fields, HashMap<String, ByteIterator> result) {
+    int responseCode;
+    try {
+      responseCode = httpGet(urlPrefix + endpoint, result);
+    } catch (Exception e) {
+      responseCode = handleExceptions(e, urlPrefix + endpoint, HttpMethod.GET);
+    }
+    if (logEnabled) {
+      System.err.println(new StringBuilder("GET Request: ").append(urlPrefix).append(endpoint)
+            .append(" | Response Code: ").append(responseCode).toString());
+    }
+    return getStatus(responseCode);
+  }
+
+  @Override
+  public Status insert(String table, String endpoint, HashMap<String, ByteIterator> values) {
+    int responseCode;
+    try {
+      responseCode = httpExecute(new HttpPost(urlPrefix + endpoint), values.get("data").toString());
+    } catch (Exception e) {
+      responseCode = handleExceptions(e, urlPrefix + endpoint, HttpMethod.POST);
+    }
+    if (logEnabled) {
+      System.err.println(new StringBuilder("POST Request: ").append(urlPrefix).append(endpoint)
+            .append(" | Response Code: ").append(responseCode).toString());
+    }
+    return getStatus(responseCode);
+  }
+
+  @Override
+  public Status delete(String table, String endpoint) {
+    int responseCode;
+    try {
+      responseCode = httpDelete(urlPrefix + endpoint);
+    } catch (Exception e) {
+      responseCode = handleExceptions(e, urlPrefix + endpoint, HttpMethod.DELETE);
+    }
+    if (logEnabled) {
+      System.err.println(new StringBuilder("DELETE Request: ").append(urlPrefix).append(endpoint)
+            .append(" | Response Code: ").append(responseCode).toString());
+    }
+    return getStatus(responseCode);
+  }
+
+  @Override
+  public Status update(String table, String endpoint, HashMap<String, ByteIterator> values) {
+    int responseCode;
+    try {
+      responseCode = httpExecute(new HttpPut(urlPrefix + endpoint), values.get("data").toString());
+    } catch (Exception e) {
+      responseCode = handleExceptions(e, urlPrefix + endpoint, HttpMethod.PUT);
+    }
+    if (logEnabled) {
+      System.err.println(new StringBuilder("PUT Request: ").append(urlPrefix).append(endpoint)
+            .append(" | Response Code: ").append(responseCode).toString());
+    }
+    return getStatus(responseCode);
+  }
+
+  @Override
+  public Status scan(String table, String startkey, int recordcount, Set<String> fields,
+      Vector<HashMap<String, ByteIterator>> result) {
+    return Status.NOT_IMPLEMENTED;
+  }
+
+  // Maps HTTP status codes to YCSB status codes.
+  private Status getStatus(int responseCode) {
+    int rc = responseCode / 100;
+    if (responseCode == 400) {
+      return Status.BAD_REQUEST;
+    } else if (responseCode == 403) {
+      return Status.FORBIDDEN;
+    } else if (responseCode == 404) {
+      return Status.NOT_FOUND;
+    } else if (responseCode == 501) {
+      return Status.NOT_IMPLEMENTED;
+    } else if (responseCode == 503) {
+      return Status.SERVICE_UNAVAILABLE;
+    } else if (rc == 5) {
+      return Status.ERROR;
+    }
+    return Status.OK;
+  }
+
+  private int handleExceptions(Exception e, String url, String method) {
+    if (logEnabled) {
+      System.err.println(new StringBuilder(method).append(" Request: ").append(url).append(" | ")
+          .append(e.getClass().getName()).append(" occured | Error message: ")
+          .append(e.getMessage()).toString());
+    }
+      
+    if (e instanceof ClientProtocolException) {
+      return 400;
+    }
+    return 500;
+  }
+
+  // Connection is automatically released back in case of an exception.
+  private int httpGet(String endpoint, HashMap<String, ByteIterator> result) throws IOException {
+    requestTimedout.setIsSatisfied(false);
+    Thread timer = new Thread(new Timer(execTimeout, requestTimedout));
+    timer.start();
+    int responseCode = 200;
+    HttpGet request = new HttpGet(endpoint);
+    for (int i = 0; i < headers.length; i = i + 2) {
+      request.setHeader(headers[i], headers[i + 1]);
+    }
+    CloseableHttpResponse response = client.execute(request);
+    responseCode = response.getStatusLine().getStatusCode();
+    HttpEntity responseEntity = response.getEntity();
+    // If null entity don't bother about connection release.
+    if (responseEntity != null) {
+      InputStream stream = responseEntity.getContent();
+      /*
+       * TODO: Gzip Compression must be supported in the future. Header[]
+       * header = response.getAllHeaders();
+       * if(response.getHeaders("Content-Encoding")[0].getValue().contains
+       * ("gzip")) stream = new GZIPInputStream(stream);
+       */
+      BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
+      StringBuffer responseContent = new StringBuffer();
+      String line = "";
+      while ((line = reader.readLine()) != null) {
+        if (requestTimedout.isSatisfied()) {
+          // Must avoid memory leak.
+          reader.close();
+          stream.close();
+          EntityUtils.consumeQuietly(responseEntity);
+          response.close();
+          client.close();
+          throw new TimeoutException();
+        }
+        responseContent.append(line);
+      }
+      timer.interrupt();
+      result.put("response", new StringByteIterator(responseContent.toString()));
+      // Closing the input stream will trigger connection release.
+      stream.close();
+    }
+    EntityUtils.consumeQuietly(responseEntity);
+    response.close();
+    client.close();
+    return responseCode;
+  }
+
+  private int httpExecute(HttpEntityEnclosingRequestBase request, String data) throws IOException {
+    requestTimedout.setIsSatisfied(false);
+    Thread timer = new Thread(new Timer(execTimeout, requestTimedout));
+    timer.start();
+    int responseCode = 200;
+    for (int i = 0; i < headers.length; i = i + 2) {
+      request.setHeader(headers[i], headers[i + 1]);
+    }
+    InputStreamEntity reqEntity = new InputStreamEntity(new ByteArrayInputStream(data.getBytes()),
+          ContentType.APPLICATION_FORM_URLENCODED);
+    reqEntity.setChunked(true);
+    request.setEntity(reqEntity);
+    CloseableHttpResponse response = client.execute(request);
+    responseCode = response.getStatusLine().getStatusCode();
+    HttpEntity responseEntity = response.getEntity();
+    // If null entity don't bother about connection release.
+    if (responseEntity != null) {
+      InputStream stream = responseEntity.getContent();
+      if (compressedResponse) {
+        stream = new GZIPInputStream(stream); 
+      }
+      BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
+      StringBuffer responseContent = new StringBuffer();
+      String line = "";
+      while ((line = reader.readLine()) != null) {
+        if (requestTimedout.isSatisfied()) {
+          // Must avoid memory leak.
+          reader.close();
+          stream.close();
+          EntityUtils.consumeQuietly(responseEntity);
+          response.close();
+          client.close();
+          throw new TimeoutException();
+        }
+        responseContent.append(line);
+      }
+      timer.interrupt();
+      // Closing the input stream will trigger connection release.
+      stream.close();
+    }
+    EntityUtils.consumeQuietly(responseEntity);
+    response.close();
+    client.close();
+    return responseCode;
+  }
+  
+  private int httpDelete(String endpoint) throws IOException {
+    requestTimedout.setIsSatisfied(false);
+    Thread timer = new Thread(new Timer(execTimeout, requestTimedout));
+    timer.start();
+    int responseCode = 200;
+    HttpDelete request = new HttpDelete(endpoint);
+    for (int i = 0; i < headers.length; i = i + 2) {
+      request.setHeader(headers[i], headers[i + 1]);
+    }
+    CloseableHttpResponse response = client.execute(request);
+    responseCode = response.getStatusLine().getStatusCode();
+    response.close();
+    client.close();
+    return responseCode;
+  }
+
+  /**
+   * Marks the input {@link Criteria} as satisfied when the input time has elapsed.
+   */
+  class Timer implements Runnable {
+
+    private long timeout;
+    private Criteria timedout;
+
+    public Timer(long timeout, Criteria timedout) {
+      this.timedout = timedout;
+      this.timeout = timeout;
+    }
+
+    @Override
+    public void run() {
+      try {
+        Thread.sleep(timeout);
+        this.timedout.setIsSatisfied(true);
+      } catch (InterruptedException e) {
+        // Do nothing.
+      }
+    }
+
+  }
+
+  /**
+   * Sets the flag when a criteria is fulfilled.
+   */
+  class Criteria {
+
+    private boolean isSatisfied;
+
+    public Criteria(boolean isSatisfied) {
+      this.isSatisfied = isSatisfied;
+    }
+
+    public boolean isSatisfied() {
+      return isSatisfied;
+    }
+
+    public void setIsSatisfied(boolean satisfied) {
+      this.isSatisfied = satisfied;
+    }
+
+  }
+
+  /**
+   * Private exception class for execution timeout.
+   */
+  class TimeoutException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+    
+    public TimeoutException() {
+      super("HTTP Request exceeded execution time limit.");
+    }
+
+  }
+
+}
diff --git a/rest/src/main/java/com/yahoo/ycsb/webservice/rest/package-info.java b/rest/src/main/java/com/yahoo/ycsb/webservice/rest/package-info.java
new file mode 100644
index 00000000..117670c9
--- /dev/null
+++ b/rest/src/main/java/com/yahoo/ycsb/webservice/rest/package-info.java
@@ -0,0 +1,22 @@
+/**
+ * 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 RESTFul Web Services.
+ */
+package com.yahoo.ycsb.webservice.rest;
+
diff --git a/rest/src/test/java/com/yahoo/ycsb/webservice/rest/IntegrationTest.java b/rest/src/test/java/com/yahoo/ycsb/webservice/rest/IntegrationTest.java
new file mode 100644
index 00000000..20d3bb7f
--- /dev/null
+++ b/rest/src/test/java/com/yahoo/ycsb/webservice/rest/IntegrationTest.java
@@ -0,0 +1,246 @@
+/**
+ * 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.webservice.rest;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
+
+import javax.servlet.ServletException;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.startup.Tomcat;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.servlet.ServletContainer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.contrib.java.lang.system.Assertion;
+import org.junit.contrib.java.lang.system.ExpectedSystemExit;
+import org.junit.runners.MethodSorters;
+
+import com.yahoo.ycsb.Client;
+import com.yahoo.ycsb.DBException;
+import com.yahoo.ycsb.webservice.rest.Utils;
+
+/**
+ * Integration test cases to verify the end to end working of the rest-binding
+ * module. It performs these steps in order. 1. Runs an embedded Tomcat
+ * server with a mock RESTFul web service. 2. Invokes the {@link Client} 
+ * class with the required parameters to start benchmarking the mock REST
+ * service. 3. Compares the response stored in the output file by {@link Client}
+ * class with the response expected. 4. Stops the embedded Tomcat server.
+ * Cases for verifying the handling of different HTTP status like 2xx & 5xx have
+ * been included in success and failure test cases.
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class IntegrationTest {
+
+  @Rule
+  public final ExpectedSystemExit exit = ExpectedSystemExit.none();
+
+  private static int port = 8080;
+  private static Tomcat tomcat;
+  private static final String WORKLOAD_FILEPATH =  IntegrationTest.class.getClassLoader().getResource("workload_rest").getPath();
+  private static final String TRACE_FILEPATH = IntegrationTest.class.getClassLoader().getResource("trace.txt").getPath();
+  private static final String ERROR_TRACE_FILEPATH = IntegrationTest.class.getClassLoader().getResource("error_trace.txt").getPath();
+  private static final String RESULTS_FILEPATH = IntegrationTest.class.getClassLoader().getResource(".").getPath() + "results.txt";
+
+  @BeforeClass
+  public static void init() throws ServletException, LifecycleException, FileNotFoundException, IOException,
+      DBException, InterruptedException {
+    String webappDirLocation =  IntegrationTest.class.getClassLoader().getResource("WebContent").getPath();
+    while (!Utils.available(port)) {
+      port++;
+    }
+    tomcat = new Tomcat();
+    tomcat.setPort(Integer.valueOf(port));
+    Context context = tomcat.addWebapp("/webService", new File(webappDirLocation).getAbsolutePath());
+    Tomcat.addServlet(context, "jersey-container-servlet", resourceConfig());
+    context.addServletMapping("/rest/*", "jersey-container-servlet");
+    tomcat.start();
+    // Allow time for proper startup.
+    Thread.sleep(1000);
+  }
+
+  @AfterClass
+  public static void cleanUp() throws LifecycleException {
+    tomcat.stop();
+  }
+
+  // All read operations during benchmark are executed successfully with an HTTP OK status.
+  @Test
+  public void testReadOpsBenchmarkSuccess() throws InterruptedException {
+    exit.expectSystemExit();
+    exit.checkAssertionAfterwards(new Assertion() {
+      @Override
+      public void checkAssertion() throws Exception {
+        List<String> results = Utils.read(RESULTS_FILEPATH);
+        assertEquals(true, results.contains("[READ], Return=OK, 1"));
+        Utils.delete(RESULTS_FILEPATH);
+      }
+    });
+    Client.main(getArgs(TRACE_FILEPATH, 1, 0, 0, 0));
+  }
+  
+  //All read operations during benchmark are executed with an HTTP 500 error.
+  @Test
+  public void testReadOpsBenchmarkFailure() throws InterruptedException {
+    exit.expectSystemExit();
+    exit.checkAssertionAfterwards(new Assertion() {
+      @Override
+      public void checkAssertion() throws Exception {
+        List<String> results = Utils.read(RESULTS_FILEPATH);
+        assertEquals(true, results.contains("[READ], Return=ERROR, 1"));
+        Utils.delete(RESULTS_FILEPATH);
+      }
+    });
+    Client.main(getArgs(ERROR_TRACE_FILEPATH, 1, 0, 0, 0));
+  }
+  
+  //All insert operations during benchmark are executed successfully with an HTTP OK status.
+  @Test
+  public void testInsertOpsBenchmarkSuccess() throws InterruptedException {
+    exit.expectSystemExit();
+    exit.checkAssertionAfterwards(new Assertion() {
+      @Override
+      public void checkAssertion() throws Exception {
+        List<String> results = Utils.read(RESULTS_FILEPATH);
+        assertEquals(true, results.contains("[INSERT], Return=OK, 1"));
+        Utils.delete(RESULTS_FILEPATH);
+      }
+    });
+    Client.main(getArgs(TRACE_FILEPATH, 0, 1, 0, 0));
+  }
+  
+  //All read operations during benchmark are executed with an HTTP 500 error.
+  @Test
+  public void testInsertOpsBenchmarkFailure() throws InterruptedException {
+    exit.expectSystemExit();
+    exit.checkAssertionAfterwards(new Assertion() {
+      @Override
+      public void checkAssertion() throws Exception {
+        List<String> results = Utils.read(RESULTS_FILEPATH);
+        assertEquals(true, results.contains("[INSERT], Return=ERROR, 1"));
+        Utils.delete(RESULTS_FILEPATH);
+      }
+    });
+    Client.main(getArgs(ERROR_TRACE_FILEPATH, 0, 1, 0, 0));
+  }
+
+  //All update operations during benchmark are executed successfully with an HTTP OK status.
+  @Test
+  public void testUpdateOpsBenchmarkSuccess() throws InterruptedException {
+    exit.expectSystemExit();
+    exit.checkAssertionAfterwards(new Assertion() {
+      @Override
+      public void checkAssertion() throws Exception {
+        List<String> results = Utils.read(RESULTS_FILEPATH);
+        assertEquals(true, results.contains("[UPDATE], Return=OK, 1"));
+        Utils.delete(RESULTS_FILEPATH);
+      }
+    });
+    Client.main(getArgs(TRACE_FILEPATH, 0, 0, 1, 0));
+  }
+  
+  //All read operations during benchmark are executed with an HTTP 500 error.
+  @Test
+  public void testUpdateOpsBenchmarkFailure() throws InterruptedException {
+    exit.expectSystemExit();
+    exit.checkAssertionAfterwards(new Assertion() {
+      @Override
+      public void checkAssertion() throws Exception {
+        List<String> results = Utils.read(RESULTS_FILEPATH);
+        assertEquals(true, results.contains("[UPDATE], Return=ERROR, 1"));
+        Utils.delete(RESULTS_FILEPATH);
+      }
+    });
+    Client.main(getArgs(ERROR_TRACE_FILEPATH, 0, 0, 1, 0));
+  }
+
+  //All delete operations during benchmark are executed successfully with an HTTP OK status.
+  @Test
+  public void testDeleteOpsBenchmarkSuccess() throws InterruptedException {
+    exit.expectSystemExit();
+    exit.checkAssertionAfterwards(new Assertion() {
+      @Override
+      public void checkAssertion() throws Exception {
+        List<String> results = Utils.read(RESULTS_FILEPATH);
+        assertEquals(true, results.contains("[DELETE], Return=OK, 1"));
+        Utils.delete(RESULTS_FILEPATH);
+      }
+    });
+    Client.main(getArgs(TRACE_FILEPATH, 0, 0, 0, 1));
+  }
+  
+  //All read operations during benchmark are executed with an HTTP 500 error.
+  @Test
+  public void testDeleteOpsBenchmarkFailure() throws InterruptedException {
+    exit.expectSystemExit();
+    exit.checkAssertionAfterwards(new Assertion() {
+      @Override
+      public void checkAssertion() throws Exception {
+        List<String> results = Utils.read(RESULTS_FILEPATH);
+        assertEquals(true, results.contains("[DELETE], Return=ERROR, 1"));
+        Utils.delete(RESULTS_FILEPATH);
+      }
+    });
+    Client.main(getArgs(ERROR_TRACE_FILEPATH, 0, 0, 0, 1));
+  }
+
+  private String[] getArgs(String traceFilePath, float rp, float ip, float up, float dp) {
+    String[] args = new String[25];
+    args[0] = "-target";
+    args[1] = "1";
+    args[2] = "-t";
+    args[3] = "-P";
+    args[4] = WORKLOAD_FILEPATH;
+    args[5] = "-p";
+    args[6] = "url.prefix=http://127.0.0.1:"+port+"/webService/rest/resource/";
+    args[7] = "-p";
+    args[8] = "url.trace.read=" + traceFilePath;
+    args[9] = "-p";
+    args[10] = "url.trace.insert=" + traceFilePath;
+    args[11] = "-p";
+    args[12] = "url.trace.update=" + traceFilePath;
+    args[13] = "-p";
+    args[14] = "url.trace.delete=" + traceFilePath;
+    args[15] = "-p";
+    args[16] = "exportfile=" + RESULTS_FILEPATH;
+    args[17] = "-p";
+    args[18] = "readproportion=" + rp;
+    args[19] = "-p";
+    args[20] = "updateproportion=" + up;
+    args[21] = "-p";
+    args[22] = "deleteproportion=" + dp;
+    args[23] = "-p";
+    args[24] = "insertproportion=" + ip;
+    return args;
+  }
+
+  private static ServletContainer resourceConfig() {
+    return new ServletContainer(new ResourceConfig(new ResourceLoader().getClasses()));
+  }
+
+}
\ No newline at end of file
diff --git a/rest/src/test/java/com/yahoo/ycsb/webservice/rest/ResourceLoader.java b/rest/src/test/java/com/yahoo/ycsb/webservice/rest/ResourceLoader.java
new file mode 100644
index 00000000..4a7a9f8e
--- /dev/null
+++ b/rest/src/test/java/com/yahoo/ycsb/webservice/rest/ResourceLoader.java
@@ -0,0 +1,38 @@
+/**
+ * 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.webservice.rest;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.core.Application;
+
+/**
+ * Class responsible for loading mock rest resource class like
+ * {@link RestTestResource}.
+ */
+public class ResourceLoader extends Application {
+
+  @Override
+  public Set<Class<?>> getClasses() {
+    final Set<Class<?>> classes = new HashSet<Class<?>>();
+    classes.add(RestTestResource.class);
+    return classes;
+  }
+
+}
\ No newline at end of file
diff --git a/rest/src/test/java/com/yahoo/ycsb/webservice/rest/RestClientTest.java b/rest/src/test/java/com/yahoo/ycsb/webservice/rest/RestClientTest.java
new file mode 100644
index 00000000..d48f1b7e
--- /dev/null
+++ b/rest/src/test/java/com/yahoo/ycsb/webservice/rest/RestClientTest.java
@@ -0,0 +1,226 @@
+/**
+ * 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.webservice.rest;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Properties;
+
+import javax.servlet.ServletException;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.startup.Tomcat;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.servlet.ServletContainer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.yahoo.ycsb.ByteIterator;
+import com.yahoo.ycsb.DBException;
+import com.yahoo.ycsb.Status;
+import com.yahoo.ycsb.StringByteIterator;
+
+/**
+ * Test cases to verify the {@link RestClient} of the rest-binding
+ * module. It performs these steps in order. 1. Runs an embedded Tomcat
+ * server with a mock RESTFul web service. 2. Invokes the {@link RestClient} 
+ * class for all the various methods which make HTTP calls to the mock REST
+ * service. 3. Compares the response from such calls to the mock REST
+ * service with the response expected. 4. Stops the embedded Tomcat server.
+ * Cases for verifying the handling of different HTTP status like 2xx, 4xx &
+ * 5xx have been included in success and failure test cases.
+ */
+public class RestClientTest {
+
+  private static Integer port = 8080;
+  private static Tomcat tomcat;
+  private static RestClient rc = new RestClient();
+  private static final String RESPONSE_TAG = "response";
+  private static final String DATA_TAG = "data";
+  private static final String VALID_RESOURCE = "resource_valid";
+  private static final String INVALID_RESOURCE = "resource_invalid";
+  private static final String ABSENT_RESOURCE = "resource_absent";
+  private static final String UNAUTHORIZED_RESOURCE = "resource_unauthorized";
+  private static final String INPUT_DATA = "<field1>one</field1><field2>two</field2>";
+
+  @BeforeClass
+  public static void init() throws IOException, DBException, ServletException, LifecycleException, InterruptedException {
+    String webappDirLocation =  IntegrationTest.class.getClassLoader().getResource("WebContent").getPath();
+    while (!Utils.available(port)) {
+      port++;
+    }
+    tomcat = new Tomcat();
+    tomcat.setPort(Integer.valueOf(port));
+    Context context = tomcat.addWebapp("/webService", new File(webappDirLocation).getAbsolutePath());
+    Tomcat.addServlet(context, "jersey-container-servlet", resourceConfig());
+    context.addServletMapping("/rest/*", "jersey-container-servlet");
+    tomcat.start();
+    // Allow time for proper startup.
+    Thread.sleep(1000);
+    Properties props = new Properties();
+    props.load(new FileReader(RestClientTest.class.getClassLoader().getResource("workload_rest").getPath()));
+    // Update the port value in the url.prefix property.
+    props.setProperty("url.prefix", props.getProperty("url.prefix").replaceAll("PORT", port.toString()));
+    rc.setProperties(props);
+    rc.init();
+  }
+
+  @AfterClass
+  public static void cleanUp() throws DBException {
+    rc.cleanup();
+  }
+  
+  // Read success.
+  @Test
+  public void read_200() {
+    HashMap<String, ByteIterator> result = new HashMap<String, ByteIterator>();
+    Status status = rc.read(null, VALID_RESOURCE, null, result);
+    assertEquals(Status.OK, status);
+    assertEquals(result.get(RESPONSE_TAG).toString(), "HTTP GET response to: "+ VALID_RESOURCE);
+  }
+  
+  // Unauthorized request error.
+  @Test
+  public void read_403() {
+    HashMap<String, ByteIterator> result = new HashMap<String, ByteIterator>();
+    Status status = rc.read(null, UNAUTHORIZED_RESOURCE, null, result);
+    assertEquals(Status.FORBIDDEN, status);
+  }
+  
+  //Not found error.
+  @Test
+  public void read_404() {
+    HashMap<String, ByteIterator> result = new HashMap<String, ByteIterator>();
+    Status status = rc.read(null, ABSENT_RESOURCE, null, result);
+    assertEquals(Status.NOT_FOUND, status);
+  }
+  
+  // Server error.
+  @Test
+  public void read_500() {
+    HashMap<String, ByteIterator> result = new HashMap<String, ByteIterator>();
+    Status status = rc.read(null, INVALID_RESOURCE, null, result);
+    assertEquals(Status.ERROR, status);
+  }
+  
+  // Insert success.
+  @Test
+  public void insert_200() {
+    HashMap<String, ByteIterator> data = new HashMap<String, ByteIterator>();
+    data.put(DATA_TAG, new StringByteIterator(INPUT_DATA));
+    Status status = rc.insert(null, VALID_RESOURCE, data);
+    assertEquals(Status.OK, status);
+  }
+  
+  @Test
+  public void insert_403() {
+    HashMap<String, ByteIterator> data = new HashMap<String, ByteIterator>();
+    data.put(DATA_TAG, new StringByteIterator(INPUT_DATA));
+    Status status = rc.insert(null, UNAUTHORIZED_RESOURCE, data);
+    assertEquals(Status.FORBIDDEN, status);
+  }
+  
+  @Test
+  public void insert_404() {
+    HashMap<String, ByteIterator> data = new HashMap<String, ByteIterator>();
+    data.put(DATA_TAG, new StringByteIterator(INPUT_DATA));
+    Status status = rc.insert(null, ABSENT_RESOURCE, data);
+    assertEquals(Status.NOT_FOUND, status);
+  }
+  
+  @Test
+  public void insert_500() {
+    HashMap<String, ByteIterator> data = new HashMap<String, ByteIterator>();
+    data.put(DATA_TAG, new StringByteIterator(INPUT_DATA));
+    Status status = rc.insert(null, INVALID_RESOURCE, data);
+    assertEquals(Status.ERROR, status);
+  }
+
+  // Delete success.
+  @Test
+  public void delete_200() {
+    Status status = rc.delete(null, VALID_RESOURCE);
+    assertEquals(Status.OK, status);
+  }
+  
+  @Test
+  public void delete_403() {
+    Status status = rc.delete(null, UNAUTHORIZED_RESOURCE);
+    assertEquals(Status.FORBIDDEN, status);
+  }
+  
+  @Test
+  public void delete_404() {
+    Status status = rc.delete(null, ABSENT_RESOURCE);
+    assertEquals(Status.NOT_FOUND, status);
+  }
+
+  @Test
+  public void delete_500() {
+    Status status = rc.delete(null, INVALID_RESOURCE);
+    assertEquals(Status.ERROR, status);
+  }
+  
+  @Test
+  public void update_200() {
+    HashMap<String, ByteIterator> data = new HashMap<String, ByteIterator>();
+    data.put(DATA_TAG, new StringByteIterator(INPUT_DATA));
+    Status status = rc.update(null, VALID_RESOURCE, data);
+    assertEquals(Status.OK, status);
+  }
+  
+  @Test
+  public void update_403() {
+    HashMap<String, ByteIterator> data = new HashMap<String, ByteIterator>();
+    data.put(DATA_TAG, new StringByteIterator(INPUT_DATA));
+    Status status = rc.update(null, UNAUTHORIZED_RESOURCE, data);
+    assertEquals(Status.FORBIDDEN, status);
+  }
+  
+  @Test
+  public void update_404() {
+    HashMap<String, ByteIterator> data = new HashMap<String, ByteIterator>();
+    data.put(DATA_TAG, new StringByteIterator(INPUT_DATA));
+    Status status = rc.update(null, ABSENT_RESOURCE, data);
+    assertEquals(Status.NOT_FOUND, status);
+  }
+  
+  @Test
+  public void update_500() {
+    HashMap<String, ByteIterator> data = new HashMap<String, ByteIterator>();
+    data.put(DATA_TAG, new StringByteIterator(INPUT_DATA));
+    Status status = rc.update(null, INVALID_RESOURCE, data);
+    assertEquals(Status.ERROR, status);
+  }
+  
+  @Test
+  public void scan() {
+    assertEquals(Status.NOT_IMPLEMENTED, rc.scan(null, null, 0, null, null));
+  }
+
+  private static ServletContainer resourceConfig() {
+    return new ServletContainer(new ResourceConfig(new ResourceLoader().getClasses()));
+  }
+
+}
diff --git a/rest/src/test/java/com/yahoo/ycsb/webservice/rest/RestTestResource.java b/rest/src/test/java/com/yahoo/ycsb/webservice/rest/RestTestResource.java
new file mode 100644
index 00000000..11de7248
--- /dev/null
+++ b/rest/src/test/java/com/yahoo/ycsb/webservice/rest/RestTestResource.java
@@ -0,0 +1,71 @@
+/**
+ * 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.webservice.rest;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+/**
+ * Class that implements a mock RESTFul web service to be used for integration
+ * testing.
+ */
+@Path("/resource/{id}")
+public class RestTestResource {
+
+  @GET
+  @Produces(MediaType.TEXT_PLAIN)
+  public Response respondToGET(@PathParam("id") String id) {
+    return processRequests(id, HttpMethod.GET);
+  }
+  
+  @POST
+  @Produces(MediaType.TEXT_PLAIN)
+  public Response respondToPOST(@PathParam("id") String id) {
+    return processRequests(id, HttpMethod.POST);
+  }
+
+  @DELETE
+  @Produces(MediaType.TEXT_PLAIN)
+  public Response respondToDELETE(@PathParam("id") String id) {
+    return processRequests(id, HttpMethod.DELETE);
+  }
+
+  @PUT
+  @Produces(MediaType.TEXT_PLAIN)
+  public Response respondToPUT(@PathParam("id") String id) {
+    return processRequests(id, HttpMethod.PUT);
+  }
+  
+  private static Response processRequests(String id, String method) {
+    if (id.equals("resource_invalid"))
+      return Response.serverError().build();
+    else if (id.equals("resource_absent"))
+      return Response.status(Response.Status.NOT_FOUND).build();
+    else if (id.equals("resource_unauthorized"))
+      return Response.status(Response.Status.FORBIDDEN).build();
+    return Response.ok("HTTP " + method + " response to: " + id).build();
+  }
+}
\ No newline at end of file
diff --git a/rest/src/test/java/com/yahoo/ycsb/webservice/rest/Utils.java b/rest/src/test/java/com/yahoo/ycsb/webservice/rest/Utils.java
new file mode 100644
index 00000000..0b8ae1bb
--- /dev/null
+++ b/rest/src/test/java/com/yahoo/ycsb/webservice/rest/Utils.java
@@ -0,0 +1,88 @@
+/**
+ * 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.webservice.rest;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.ServerSocket;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Holds the common utility methods.
+ */
+public class Utils {
+
+  /**
+   * Returns true if the port is available.
+   * 
+   * @param port
+   * @return isAvailable
+   */
+  public static boolean available(int port) {
+    ServerSocket ss = null;
+    DatagramSocket ds = null;
+    try {
+      ss = new ServerSocket(port);
+      ss.setReuseAddress(true);
+      ds = new DatagramSocket(port);
+      ds.setReuseAddress(true);
+      return true;
+    } catch (IOException e) {
+    } finally {
+      if (ds != null) {
+        ds.close();
+      }
+      if (ss != null) {
+        try {
+          ss.close();
+        } catch (IOException e) {
+          /* should not be thrown */
+        }
+      }
+    }
+    return false;
+  }
+
+  public static List<String> read(String filepath) {
+    List<String> list = new ArrayList<String>();
+    try {
+      BufferedReader file = new BufferedReader(new FileReader(filepath));
+      String line = null;
+      while ((line = file.readLine()) != null) {
+        list.add(line.trim());
+      }
+      file.close();
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+    return list;
+  }
+
+  public static void delete(String filepath) {
+    try {
+      new File(filepath).delete();
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+}
diff --git a/rest/src/test/resources/WebContent/index.html b/rest/src/test/resources/WebContent/index.html
new file mode 100644
index 00000000..ded87fc2
--- /dev/null
+++ b/rest/src/test/resources/WebContent/index.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ 
+ <head>
+  <meta charset="ISO-8859-1">
+  <title>rest-binding</title>
+ </head>
+
+ <body>
+  Welcome to the rest-binding integration test cases!
+ </body>
+
+</html>
\ No newline at end of file
diff --git a/rest/src/test/resources/error_trace.txt b/rest/src/test/resources/error_trace.txt
new file mode 100644
index 00000000..18ff9cd6
--- /dev/null
+++ b/rest/src/test/resources/error_trace.txt
@@ -0,0 +1 @@
+resource_invalid
\ No newline at end of file
diff --git a/rest/src/test/resources/trace.txt b/rest/src/test/resources/trace.txt
new file mode 100644
index 00000000..65a600da
--- /dev/null
+++ b/rest/src/test/resources/trace.txt
@@ -0,0 +1,5 @@
+resource_1
+resource_2
+resource_3
+resource_4
+resource_5
\ No newline at end of file
diff --git a/rest/src/test/resources/workload_rest b/rest/src/test/resources/workload_rest
new file mode 100644
index 00000000..e4df8323
--- /dev/null
+++ b/rest/src/test/resources/workload_rest
@@ -0,0 +1,68 @@
+# Copyright (c) 2016 Yahoo! Inc. 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.                                                                                                                                                                   
+
+
+# Yahoo! Cloud System Benchmark
+# Workload A: Update heavy workload
+#   Application example: Session store recording recent actions
+#                        
+#   Read/update ratio: 50/50
+#   Default data size: 1 KB records (10 fields, 100 bytes each, plus key)
+#   Request distribution: zipfian
+
+#	Core Properties
+workload=com.yahoo.ycsb.workloads.RestWorkload
+db=com.yahoo.ycsb.webservice.rest.RestClient
+exporter=com.yahoo.ycsb.measurements.exporter.TextMeasurementsExporter
+threadcount=1
+fieldlengthdistribution=uniform
+measurementtype=hdrhistogram
+
+#	Workload Properties
+fieldcount=1
+fieldlength=2500
+readproportion=1
+updateproportion=0
+deleteproportion=0
+insertproportion=0
+requestdistribution=zipfian
+operationcount=1
+maxexecutiontime=720
+
+#	Custom Properties
+url.prefix=http://127.0.0.1:PORT/webService/rest/resource/
+url.trace.read=/src/test/resource/trace.txt
+url.trace.insert=/src/test/resource/trace.txt
+url.trace.update=/src/test/resource/trace.txt
+url.trace.delete=/src/test/resource/trace.txt
+# Header must be separated by space. Other delimiters might occur as header values and hence can not be used.
+headers=Accept */* Accept-Language en-US,en;q=0.5 Content-Type application/x-www-form-urlencoded user-agent Mozilla/5.0 Connection close
+timeout.con=60
+timeout.read=60
+timeout.exec=60
+log.enable=false
+readrecordcount=10000
+insertrecordcount=5000
+deleterecordcount=1000
+updaterecordcount=1000
+readzipfconstant=0.9
+insertzipfconstant=0.9
+updatezipfconstant=0.9
+deletezipfconstant=0.9
+
+
+#	Measurement Properties
+hdrhistogram.percentiles=50,90,95,99
+histogram.buckets=1
\ No newline at end of file
-- 
GitLab