You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by na...@apache.org on 2016/02/18 10:32:21 UTC

jclouds-labs git commit: Initial implementation of Keys API

Repository: jclouds-labs
Updated Branches:
  refs/heads/master cfb4311f2 -> 7b21c2e6a


Initial implementation of Keys API


Project: http://git-wip-us.apache.org/repos/asf/jclouds-labs/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds-labs/commit/7b21c2e6
Tree: http://git-wip-us.apache.org/repos/asf/jclouds-labs/tree/7b21c2e6
Diff: http://git-wip-us.apache.org/repos/asf/jclouds-labs/diff/7b21c2e6

Branch: refs/heads/master
Commit: 7b21c2e6a74ab0dc7c23cc3f0cd70fdbfde22538
Parents: cfb4311
Author: Christopher Dancy <da...@pega.com>
Authored: Sat Feb 13 15:07:13 2016 -0500
Committer: Ignasi Barrera <na...@apache.org>
Committed: Thu Feb 18 10:22:49 2016 +0100

----------------------------------------------------------------------
 .../src/main/java/org/jclouds/etcd/EtcdApi.java |   6 +-
 .../java/org/jclouds/etcd/EtcdApiMetadata.java  |   2 +-
 .../java/org/jclouds/etcd/domain/keys/Key.java  |  42 ++++++
 .../java/org/jclouds/etcd/domain/keys/Node.java |  60 ++++++++
 .../jclouds/etcd/fallbacks/EtcdFallbacks.java   |  12 +-
 .../java/org/jclouds/etcd/features/KeysApi.java |  59 ++++++++
 .../jclouds/etcd/handlers/EtcdErrorHandler.java |   4 -
 .../org/jclouds/etcd/BaseEtcdApiLiveTest.java   |   5 +
 .../jclouds/etcd/features/KeysApiLiveTest.java  | 100 +++++++++++++
 .../jclouds/etcd/features/KeysApiMockTest.java  | 144 +++++++++++++++++++
 .../etcd/features/MembersApiLiveTest.java       |   7 +-
 .../jclouds/etcd/internal/BaseEtcdMockTest.java |  14 +-
 etcd/src/test/resources/keys-create-ttl.json    |  11 ++
 etcd/src/test/resources/keys-create.json        |   9 ++
 etcd/src/test/resources/keys-delete.json        |  14 ++
 .../resources/keys-get-delete-nonexistent.json  |   6 +
 etcd/src/test/resources/keys-get.json           |   9 ++
 17 files changed, 492 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/7b21c2e6/etcd/src/main/java/org/jclouds/etcd/EtcdApi.java
----------------------------------------------------------------------
diff --git a/etcd/src/main/java/org/jclouds/etcd/EtcdApi.java b/etcd/src/main/java/org/jclouds/etcd/EtcdApi.java
index 6573435..fb5f811 100644
--- a/etcd/src/main/java/org/jclouds/etcd/EtcdApi.java
+++ b/etcd/src/main/java/org/jclouds/etcd/EtcdApi.java
@@ -19,14 +19,18 @@ package org.jclouds.etcd;
 
 import java.io.Closeable;
 
-import org.jclouds.etcd.features.StatisticsApi;
+import org.jclouds.etcd.features.KeysApi;
 import org.jclouds.etcd.features.MembersApi;
 import org.jclouds.etcd.features.MiscellaneousApi;
+import org.jclouds.etcd.features.StatisticsApi;
 import org.jclouds.rest.annotations.Delegate;
 
 public interface EtcdApi extends Closeable {
 
    @Delegate
+   KeysApi keysApi();
+
+   @Delegate
    MembersApi membersApi();
 
    @Delegate

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/7b21c2e6/etcd/src/main/java/org/jclouds/etcd/EtcdApiMetadata.java
----------------------------------------------------------------------
diff --git a/etcd/src/main/java/org/jclouds/etcd/EtcdApiMetadata.java b/etcd/src/main/java/org/jclouds/etcd/EtcdApiMetadata.java
index 7b30457..c44490f 100644
--- a/etcd/src/main/java/org/jclouds/etcd/EtcdApiMetadata.java
+++ b/etcd/src/main/java/org/jclouds/etcd/EtcdApiMetadata.java
@@ -32,7 +32,7 @@ import com.google.inject.Module;
 public class EtcdApiMetadata extends BaseHttpApiMetadata<EtcdApi> {
 
    public static final String API_VERSION = "v2";
-   public static final String BUILD_VERSION = "2.1.1";
+   public static final String BUILD_VERSION = "2.2.5";
 
    @Override
    public Builder toBuilder() {

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/7b21c2e6/etcd/src/main/java/org/jclouds/etcd/domain/keys/Key.java
----------------------------------------------------------------------
diff --git a/etcd/src/main/java/org/jclouds/etcd/domain/keys/Key.java b/etcd/src/main/java/org/jclouds/etcd/domain/keys/Key.java
new file mode 100644
index 0000000..5c9b378
--- /dev/null
+++ b/etcd/src/main/java/org/jclouds/etcd/domain/keys/Key.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jclouds.etcd.domain.keys;
+
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class Key {
+
+   public abstract String action();
+
+   public abstract Node node();
+
+   @Nullable
+   public abstract Node prevNode();
+
+   Key() {
+   }
+
+   @SerializedNames({ "action", "node", "prevNode" })
+   public static Key create(String action, Node node, Node prevNode) {
+      return new AutoValue_Key(action, node, prevNode);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/7b21c2e6/etcd/src/main/java/org/jclouds/etcd/domain/keys/Node.java
----------------------------------------------------------------------
diff --git a/etcd/src/main/java/org/jclouds/etcd/domain/keys/Node.java b/etcd/src/main/java/org/jclouds/etcd/domain/keys/Node.java
new file mode 100644
index 0000000..6ccdac9
--- /dev/null
+++ b/etcd/src/main/java/org/jclouds/etcd/domain/keys/Node.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jclouds.etcd.domain.keys;
+
+import java.util.List;
+
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+
+@AutoValue
+public abstract class Node {
+
+   public abstract int createdIndex();
+
+   public abstract boolean dir();
+
+   public abstract List<Node> nodes();
+
+   @Nullable
+   public abstract String expiration();
+
+   @Nullable
+   public abstract String key();
+
+   public abstract int modifiedIndex();
+
+   public abstract int ttl();
+
+   @Nullable
+   public abstract String value();
+
+   Node() {
+   }
+
+   @SerializedNames({ "createdIndex", "dir", "nodes", "expiration", "key", "modifiedIndex", "ttl", "value" })
+   public static Node create(int createdIndex, boolean dir, List<Node> nodes, String expiration, String key,
+         int modifiedIndex, int ttl, String value) {
+      return new AutoValue_Node(createdIndex, dir,
+            nodes != null ? ImmutableList.copyOf(nodes) : ImmutableList.<Node> of(), expiration, key, modifiedIndex,
+            ttl, value);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/7b21c2e6/etcd/src/main/java/org/jclouds/etcd/fallbacks/EtcdFallbacks.java
----------------------------------------------------------------------
diff --git a/etcd/src/main/java/org/jclouds/etcd/fallbacks/EtcdFallbacks.java b/etcd/src/main/java/org/jclouds/etcd/fallbacks/EtcdFallbacks.java
index 95152a7..de8cec9 100644
--- a/etcd/src/main/java/org/jclouds/etcd/fallbacks/EtcdFallbacks.java
+++ b/etcd/src/main/java/org/jclouds/etcd/fallbacks/EtcdFallbacks.java
@@ -19,10 +19,10 @@ package org.jclouds.etcd.fallbacks;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Predicates.equalTo;
 import static com.google.common.base.Throwables.propagate;
-
 import static org.jclouds.http.HttpUtils.returnValueOnCodeOrNull;
 
 import org.jclouds.Fallback;
+import org.jclouds.http.HttpUtils;
 
 public final class EtcdFallbacks {
 
@@ -35,4 +35,14 @@ public final class EtcdFallbacks {
          throw propagate(t);
       }
    }
+
+   public static final class NullOnKeyNonFoundAnd404 implements Fallback<Object> {
+      public Object createOrPropagate(Throwable t) throws Exception {
+         if (checkNotNull(t, "throwable") != null && t.getMessage().contains("Key not found")
+               && HttpUtils.contains404(t)) {
+            return null;
+         }
+         throw propagate(t);
+      }
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/7b21c2e6/etcd/src/main/java/org/jclouds/etcd/features/KeysApi.java
----------------------------------------------------------------------
diff --git a/etcd/src/main/java/org/jclouds/etcd/features/KeysApi.java b/etcd/src/main/java/org/jclouds/etcd/features/KeysApi.java
new file mode 100644
index 0000000..f2ef5d6
--- /dev/null
+++ b/etcd/src/main/java/org/jclouds/etcd/features/KeysApi.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jclouds.etcd.features;
+
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.etcd.domain.keys.Key;
+import org.jclouds.etcd.fallbacks.EtcdFallbacks.NullOnKeyNonFoundAnd404;
+import org.jclouds.rest.annotations.Fallback;
+
+@Consumes(MediaType.APPLICATION_JSON)
+@Path("/{jclouds.api-version}/keys")
+public interface KeysApi {
+
+   @Named("keys:create")
+   @PUT
+   @Path("/{key}")
+   Key createKey(@PathParam("key") String key, @FormParam("value") String value);
+
+   @Named("keys:create")
+   @PUT
+   @Path("/{key}")
+   Key createKey(@PathParam("key") String key, @FormParam("value") String value, @FormParam("ttl") int seconds);
+
+   @Named("keys:get")
+   @GET
+   @Path("/{key}")
+   @Fallback(NullOnKeyNonFoundAnd404.class)
+   Key getKey(@PathParam("key") String key);
+
+   @Named("keys:delete")
+   @DELETE
+   @Path("/{key}")
+   @Fallback(NullOnKeyNonFoundAnd404.class)
+   Key deleteKey(@PathParam("key") String key);
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/7b21c2e6/etcd/src/main/java/org/jclouds/etcd/handlers/EtcdErrorHandler.java
----------------------------------------------------------------------
diff --git a/etcd/src/main/java/org/jclouds/etcd/handlers/EtcdErrorHandler.java b/etcd/src/main/java/org/jclouds/etcd/handlers/EtcdErrorHandler.java
index 4784e44..e3e2261 100644
--- a/etcd/src/main/java/org/jclouds/etcd/handlers/EtcdErrorHandler.java
+++ b/etcd/src/main/java/org/jclouds/etcd/handlers/EtcdErrorHandler.java
@@ -28,7 +28,6 @@ import org.jclouds.http.HttpResponse;
 import org.jclouds.http.HttpResponseException;
 import org.jclouds.logging.Logger;
 import org.jclouds.rest.ResourceAlreadyExistsException;
-import org.jclouds.rest.ResourceNotFoundException;
 import org.jclouds.util.Strings2;
 
 import com.google.common.base.Throwables;
@@ -53,9 +52,6 @@ public class EtcdErrorHandler implements HttpErrorHandler {
             case 400:
                exception = new IllegalArgumentException(message);
                break;
-            case 404:
-               exception = new ResourceNotFoundException(message);
-               break;
             case 409:
                exception = new ResourceAlreadyExistsException(message);
                break;

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/7b21c2e6/etcd/src/test/java/org/jclouds/etcd/BaseEtcdApiLiveTest.java
----------------------------------------------------------------------
diff --git a/etcd/src/test/java/org/jclouds/etcd/BaseEtcdApiLiveTest.java b/etcd/src/test/java/org/jclouds/etcd/BaseEtcdApiLiveTest.java
index ac7431d..88cfb61 100644
--- a/etcd/src/test/java/org/jclouds/etcd/BaseEtcdApiLiveTest.java
+++ b/etcd/src/test/java/org/jclouds/etcd/BaseEtcdApiLiveTest.java
@@ -17,6 +17,7 @@
 package org.jclouds.etcd;
 
 import java.util.Properties;
+import java.util.UUID;
 
 import org.jclouds.Constants;
 import org.jclouds.apis.BaseApiLiveTest;
@@ -43,4 +44,8 @@ public class BaseEtcdApiLiveTest extends BaseApiLiveTest<EtcdApi> {
       overrides.setProperty(Constants.PROPERTY_MAX_RETRIES, "0");
       return overrides;
    }
+
+   protected String randomString() {
+      return UUID.randomUUID().toString().replaceAll("-", "");
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/7b21c2e6/etcd/src/test/java/org/jclouds/etcd/features/KeysApiLiveTest.java
----------------------------------------------------------------------
diff --git a/etcd/src/test/java/org/jclouds/etcd/features/KeysApiLiveTest.java b/etcd/src/test/java/org/jclouds/etcd/features/KeysApiLiveTest.java
new file mode 100644
index 0000000..2b31917
--- /dev/null
+++ b/etcd/src/test/java/org/jclouds/etcd/features/KeysApiLiveTest.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.etcd.features;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import org.jclouds.etcd.BaseEtcdApiLiveTest;
+import org.jclouds.etcd.domain.keys.Key;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Throwables;
+
+@Test(groups = "live", testName = "KeysApiLiveTest")
+public class KeysApiLiveTest extends BaseEtcdApiLiveTest {
+
+   private String key;
+   private String value;
+
+   @BeforeClass
+   protected void init() {
+      key = randomString();
+      value = randomString();
+   }
+
+   @Test
+   public void testCreateKeyWithTTL() {
+      String localKey = randomString();
+      String localValue = randomString();
+      Key createdKey = api().createKey(localKey, localValue, 1);
+      assertNotNull(createdKey);
+      assertNotNull(createdKey.node().expiration());
+      assertTrue(createdKey.node().ttl() == 1);
+
+      try {
+         Thread.sleep(3000);
+      } catch (InterruptedException e) {
+         Throwables.propagate(e);
+      }
+
+      createdKey = api().getKey(localKey);
+      assertNull(createdKey);
+   }
+
+   @Test
+   public void testCreateKey() {
+      Key createdKey = api().createKey(key, value);
+      assertNotNull(createdKey);
+      assertTrue(createdKey.action().equals("set"));
+      assertTrue(createdKey.node().value().equals(value));
+   }
+
+   @Test(dependsOnMethods = "testCreateKey")
+   public void testGetKey() {
+      Key getKey = api().getKey(key);
+      assertNotNull(getKey);
+      assertTrue(getKey.action().equals("get"));
+      assertTrue(getKey.node().value().equals(value));
+   }
+
+   @Test(dependsOnMethods = "testGetKey", alwaysRun = true)
+   public void testDeleteKey() {
+      Key deletedKey = api().deleteKey(key);
+      assertNotNull(deletedKey);
+      assertTrue(deletedKey.action().equals("delete"));
+      assertTrue(deletedKey.prevNode().value().equals(value));
+   }
+
+   @Test
+   public void testGetNonExistentKey() {
+      Key deletedKey = api().getKey(randomString());
+      assertNull(deletedKey);
+   }
+
+   @Test
+   public void testDeleteNonExistentKey() {
+      Key deletedKey = api().deleteKey(randomString());
+      assertNull(deletedKey);
+   }
+
+   private KeysApi api() {
+      return api.keysApi();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/7b21c2e6/etcd/src/test/java/org/jclouds/etcd/features/KeysApiMockTest.java
----------------------------------------------------------------------
diff --git a/etcd/src/test/java/org/jclouds/etcd/features/KeysApiMockTest.java b/etcd/src/test/java/org/jclouds/etcd/features/KeysApiMockTest.java
new file mode 100644
index 0000000..7f650a0
--- /dev/null
+++ b/etcd/src/test/java/org/jclouds/etcd/features/KeysApiMockTest.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.etcd.features;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import org.jclouds.etcd.EtcdApi;
+import org.jclouds.etcd.EtcdApiMetadata;
+import org.jclouds.etcd.domain.keys.Key;
+import org.jclouds.etcd.internal.BaseEtcdMockTest;
+import org.testng.annotations.Test;
+
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+
+/**
+ * Mock tests for the {@link org.jclouds.etcd.features.KeysApi} class.
+ */
+@Test(groups = "unit", testName = "KeysApiMockTest")
+public class KeysApiMockTest extends BaseEtcdMockTest {
+
+   public void testCreateKey() throws Exception {
+      MockWebServer server = mockEtcdJavaWebServer();
+
+      server.enqueue(new MockResponse().setBody(payloadFromResource("/keys-create.json")).setResponseCode(201));
+      EtcdApi etcdApi = api(server.getUrl("/"));
+      KeysApi api = etcdApi.keysApi();
+      try {
+         Key createdKey = api.createKey("hello", "world");
+         assertNotNull(createdKey);
+         assertTrue(createdKey.node().key().equals("/hello"));
+         assertTrue(createdKey.node().value().equals("world"));
+         assertSentWithFormData(server, "PUT", "/" + EtcdApiMetadata.API_VERSION + "/keys/hello", "value=world");
+      } finally {
+         etcdApi.close();
+         server.shutdown();
+      }
+   }
+
+   public void testCreateKeyWithTTL() throws Exception {
+      MockWebServer server = mockEtcdJavaWebServer();
+
+      server.enqueue(new MockResponse().setBody(payloadFromResource("/keys-create-ttl.json")).setResponseCode(201));
+      EtcdApi etcdApi = api(server.getUrl("/"));
+      KeysApi api = etcdApi.keysApi();
+      try {
+         Key createdKey = api.createKey("hello", "world", 5);
+         assertNotNull(createdKey);
+         assertNotNull(createdKey.node().expiration());
+         assertTrue(createdKey.node().ttl() == 5);
+         assertTrue(createdKey.node().key().equals("/hello"));
+         assertTrue(createdKey.node().value().equals("world"));
+         assertSentWithFormData(server, "PUT", "/" + EtcdApiMetadata.API_VERSION + "/keys/hello", "value=world&ttl=5");
+      } finally {
+         etcdApi.close();
+         server.shutdown();
+      }
+   }
+
+   public void testGetKey() throws Exception {
+      MockWebServer server = mockEtcdJavaWebServer();
+
+      server.enqueue(new MockResponse().setBody(payloadFromResource("/keys-get.json")).setResponseCode(200));
+      EtcdApi etcdApi = api(server.getUrl("/"));
+      KeysApi api = etcdApi.keysApi();
+      try {
+         Key foundKey = api.getKey("hello");
+         assertNotNull(foundKey);
+         assertTrue(foundKey.node().key().equals("/hello"));
+         assertTrue(foundKey.node().value().equals("world"));
+         assertSent(server, "GET", "/" + EtcdApiMetadata.API_VERSION + "/keys/hello");
+      } finally {
+         etcdApi.close();
+         server.shutdown();
+      }
+   }
+
+   public void testGetNonExistentKey() throws Exception {
+      MockWebServer server = mockEtcdJavaWebServer();
+
+      server.enqueue(
+            new MockResponse().setBody(payloadFromResource("/keys-get-delete-nonexistent.json")).setResponseCode(404));
+      EtcdApi etcdApi = api(server.getUrl("/"));
+      KeysApi api = etcdApi.keysApi();
+      try {
+         Key nonExistentKey = api.getKey("NonExistentKeyToGet");
+         assertNull(nonExistentKey);
+         assertSent(server, "GET", "/" + EtcdApiMetadata.API_VERSION + "/keys/NonExistentKeyToGet");
+      } finally {
+         etcdApi.close();
+         server.shutdown();
+      }
+   }
+
+   public void testDeleteKey() throws Exception {
+      MockWebServer server = mockEtcdJavaWebServer();
+
+      server.enqueue(new MockResponse().setBody(payloadFromResource("/keys-delete.json")).setResponseCode(200));
+      EtcdApi etcdApi = api(server.getUrl("/"));
+      KeysApi api = etcdApi.keysApi();
+      try {
+         Key deletedKey = api.deleteKey("hello");
+         assertTrue(deletedKey.prevNode().key().equals("/hello"));
+         assertTrue(deletedKey.prevNode().value().equals("world"));
+         assertSent(server, "DELETE", "/" + EtcdApiMetadata.API_VERSION + "/keys/hello");
+      } finally {
+         etcdApi.close();
+         server.shutdown();
+      }
+   }
+
+   public void testDeleteNonExistentKey() throws Exception {
+      MockWebServer server = mockEtcdJavaWebServer();
+
+      server.enqueue(
+            new MockResponse().setBody(payloadFromResource("/keys-get-delete-nonexistent.json")).setResponseCode(404));
+      EtcdApi etcdApi = api(server.getUrl("/"));
+      KeysApi api = etcdApi.keysApi();
+      try {
+         Key nonExistentKey = api.deleteKey("NonExistentKeyToDelete");
+         assertNull(nonExistentKey);
+         assertSent(server, "DELETE", "/" + EtcdApiMetadata.API_VERSION + "/keys/NonExistentKeyToDelete");
+      } finally {
+         etcdApi.close();
+         server.shutdown();
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/7b21c2e6/etcd/src/test/java/org/jclouds/etcd/features/MembersApiLiveTest.java
----------------------------------------------------------------------
diff --git a/etcd/src/test/java/org/jclouds/etcd/features/MembersApiLiveTest.java b/etcd/src/test/java/org/jclouds/etcd/features/MembersApiLiveTest.java
index 4935a60..df8d58e 100644
--- a/etcd/src/test/java/org/jclouds/etcd/features/MembersApiLiveTest.java
+++ b/etcd/src/test/java/org/jclouds/etcd/features/MembersApiLiveTest.java
@@ -16,13 +16,12 @@
  */
 package org.jclouds.etcd.features;
 
-import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.assertFalse;
 
 import java.util.List;
-import java.util.UUID;
 
 import org.jclouds.etcd.BaseEtcdApiLiveTest;
 import org.jclouds.etcd.domain.members.CreateMember;
@@ -94,7 +93,7 @@ public class MembersApiLiveTest extends BaseEtcdApiLiveTest {
 
    @Test(dependsOnMethods = "testAddMemberWithIllegalFormat")
    public void testDeleteMemberNonExistentMember() {
-      boolean successful = api().delete(UUID.randomUUID().toString().replaceAll("-", ""));
+      boolean successful = api().delete(randomString());
       assertFalse(successful);
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/7b21c2e6/etcd/src/test/java/org/jclouds/etcd/internal/BaseEtcdMockTest.java
----------------------------------------------------------------------
diff --git a/etcd/src/test/java/org/jclouds/etcd/internal/BaseEtcdMockTest.java b/etcd/src/test/java/org/jclouds/etcd/internal/BaseEtcdMockTest.java
index 8cd85eb..9358d09 100644
--- a/etcd/src/test/java/org/jclouds/etcd/internal/BaseEtcdMockTest.java
+++ b/etcd/src/test/java/org/jclouds/etcd/internal/BaseEtcdMockTest.java
@@ -17,14 +17,15 @@
 
 package org.jclouds.etcd.internal;
 
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.jclouds.util.Strings2.toStringAndClose;
-import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 import static org.testng.Assert.assertEquals;
 
 import java.io.IOException;
 import java.net.URL;
 import java.util.Properties;
+
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 
@@ -83,6 +84,17 @@ public class BaseEtcdMockTest {
       return request;
    }
 
+   protected RecordedRequest assertSentWithFormData(MockWebServer server, String method, String path, String body)
+         throws InterruptedException {
+      RecordedRequest request = server.takeRequest();
+      assertThat(request.getMethod()).isEqualTo(method);
+      assertThat(request.getPath()).isEqualTo(path);
+      assertThat(request.getUtf8Body()).isEqualTo(body);
+      assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON);
+      assertThat(request.getHeader(HttpHeaders.CONTENT_TYPE)).isEqualTo(MediaType.APPLICATION_FORM_URLENCODED);
+      return request;
+   }
+
    protected RecordedRequest assertSentAcceptText(MockWebServer server, String method, String path)
          throws InterruptedException {
       RecordedRequest request = server.takeRequest();

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/7b21c2e6/etcd/src/test/resources/keys-create-ttl.json
----------------------------------------------------------------------
diff --git a/etcd/src/test/resources/keys-create-ttl.json b/etcd/src/test/resources/keys-create-ttl.json
new file mode 100644
index 0000000..aa1b94a
--- /dev/null
+++ b/etcd/src/test/resources/keys-create-ttl.json
@@ -0,0 +1,11 @@
+{  
+   "action":"set",
+   "node":{  
+      "createdIndex":5,
+      "expiration":"2016-02-16T13:33:21.970469165Z",
+      "key":"/hello",
+      "modifiedIndex":5,
+      "ttl":5,
+      "value":"world"
+   }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/7b21c2e6/etcd/src/test/resources/keys-create.json
----------------------------------------------------------------------
diff --git a/etcd/src/test/resources/keys-create.json b/etcd/src/test/resources/keys-create.json
new file mode 100644
index 0000000..79cb39e
--- /dev/null
+++ b/etcd/src/test/resources/keys-create.json
@@ -0,0 +1,9 @@
+{  
+   "action":"set",
+   "node":{  
+      "key":"/hello",
+      "value":"world",
+      "modifiedIndex":40,
+      "createdIndex":40
+   }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/7b21c2e6/etcd/src/test/resources/keys-delete.json
----------------------------------------------------------------------
diff --git a/etcd/src/test/resources/keys-delete.json b/etcd/src/test/resources/keys-delete.json
new file mode 100644
index 0000000..9e8d234
--- /dev/null
+++ b/etcd/src/test/resources/keys-delete.json
@@ -0,0 +1,14 @@
+{  
+   "action":"delete",
+   "node":{  
+      "key":"/hello",
+      "modifiedIndex":41,
+      "createdIndex":39
+   },
+   "prevNode":{  
+      "key":"/hello",
+      "value":"world",
+      "modifiedIndex":39,
+      "createdIndex":39
+   }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/7b21c2e6/etcd/src/test/resources/keys-get-delete-nonexistent.json
----------------------------------------------------------------------
diff --git a/etcd/src/test/resources/keys-get-delete-nonexistent.json b/etcd/src/test/resources/keys-get-delete-nonexistent.json
new file mode 100644
index 0000000..3a69156
--- /dev/null
+++ b/etcd/src/test/resources/keys-get-delete-nonexistent.json
@@ -0,0 +1,6 @@
+{  
+   "errorCode":100,
+   "message":"Key not found",
+   "cause":"/foo",
+   "index":16
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/7b21c2e6/etcd/src/test/resources/keys-get.json
----------------------------------------------------------------------
diff --git a/etcd/src/test/resources/keys-get.json b/etcd/src/test/resources/keys-get.json
new file mode 100644
index 0000000..19213c4
--- /dev/null
+++ b/etcd/src/test/resources/keys-get.json
@@ -0,0 +1,9 @@
+{  
+   "action":"get",
+   "node":{  
+      "key":"/hello",
+      "value":"world",
+      "modifiedIndex":39,
+      "createdIndex":39
+   }
+}
\ No newline at end of file