You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tamaya.apache.org by an...@apache.org on 2016/01/07 11:36:52 UTC

[2/2] incubator-tamaya git commit: TAMAYA-133: Tested and stabilized etcd read-only support and also write operations (backend side).

TAMAYA-133: Tested and stabilized etcd read-only support and also write operations (backend side).


Project: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/commit/ba5d9571
Tree: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/tree/ba5d9571
Diff: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/diff/ba5d9571

Branch: refs/heads/master
Commit: ba5d9571fd1b93b890044ef5631bf879b5e36816
Parents: a847dca
Author: anatole <an...@apache.org>
Authored: Thu Jan 7 11:36:44 2016 +0100
Committer: anatole <an...@apache.org>
Committed: Thu Jan 7 11:36:44 2016 +0100

----------------------------------------------------------------------
 .../org/apache/tamaya/etcd/EtcdAccessor.java    |  82 +++++++------
 .../apache/tamaya/etcd/EtcdPropertySource.java  |  20 +++-
 .../apache/tamaya/etcd/EtcdAccessorTest.java    | 116 +++++++++++++++++++
 .../tamaya/etcd/EtcdPropertySourceTest.java     |  74 ++++++++++++
 modules/integration/pom.xml                     |   1 +
 5 files changed, 256 insertions(+), 37 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/ba5d9571/modules/integration/etcd/src/main/java/org/apache/tamaya/etcd/EtcdAccessor.java
----------------------------------------------------------------------
diff --git a/modules/integration/etcd/src/main/java/org/apache/tamaya/etcd/EtcdAccessor.java b/modules/integration/etcd/src/main/java/org/apache/tamaya/etcd/EtcdAccessor.java
index 101a1cd..d7c0f0c 100644
--- a/modules/integration/etcd/src/main/java/org/apache/tamaya/etcd/EtcdAccessor.java
+++ b/modules/integration/etcd/src/main/java/org/apache/tamaya/etcd/EtcdAccessor.java
@@ -43,6 +43,7 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.logging.Level;
 import java.util.logging.Logger;
 
 /**
@@ -112,13 +113,13 @@ public class EtcdAccessor {
             }
             return version;
         } catch(Exception e){
-            // TODO log error
+            LOG.log(Level.SEVERE, "Error getting etcd version from: " + serverURL, e);
         } finally {
             if(response!=null){
                 try {
                     response.close();
                 } catch (IOException e) {
-                    // TODO log error
+                    LOG.log(Level.WARNING, "Failed to close http response", e);
                 }
             }
         }
@@ -153,7 +154,6 @@ public class EtcdAccessor {
     public Map<String,String> get(String key){
         CloseableHttpResponse response = null;
         Map<String,String> result = new HashMap<>();
-        result.put("_" + key +".source", "[etcd]"+serverURL);
         try {
             HttpGet httpGet = new HttpGet(serverURL + "/v2/keys/"+key);
             response = httpclient.execute(httpGet);
@@ -162,6 +162,8 @@ public class EtcdAccessor {
                 JsonReader reader = readerFactory.createReader(new StringReader(EntityUtils.toString(entity)));
                 JsonObject o = reader.readObject();
                 JsonObject node = o.getJsonObject("node");
+                result.put(key, node.getString("value"));
+                result.put("_" + key +".source", "[etcd]"+serverURL);
                 if(node.containsKey("createdIndex")) {
                     result.put("_" + key +".createdIndex", String.valueOf(node.getInt("createdIndex")));
                 }
@@ -169,23 +171,24 @@ public class EtcdAccessor {
                     result.put("_" + key +".modifiedIndex", String.valueOf(node.getInt("modifiedIndex")));
                 }
                 if(node.containsKey("expiration")) {
-                    result.put("_" + key +".expiration", String.valueOf(node.getInt("expiration")));
+                    result.put("_" + key +".expiration", String.valueOf(node.getString("expiration")));
                 }
-                if(node.containsKey("_" + key +".ttl")) {
+                if(node.containsKey("ttl")) {
                     result.put("_" + key +".ttl", String.valueOf(node.getInt("ttl")));
                 }
-                result.put(key, o.getString("value"));
                 EntityUtils.consume(entity);
+            }else{
+                result.put("_" + key +".NOT_FOUND.target", "[etcd]"+serverURL);
             }
         } catch(Exception e){
-            // TODO log error
-            result.put("_" + key +".error", e.toString());
+            LOG.log(Level.SEVERE, "Error reading key '"+key+"' from etcd: " + serverURL, e);
+            result.put("_ERROR", "Error reading key '"+key+"' from etcd: " + serverURL + ": " + e.toString());
         } finally {
             if(response!=null){
                 try {
                     response.close();
                 } catch (IOException e) {
-                    // TODO log error
+                    LOG.log(Level.WARNING, "Failed to close http response", e);
                 }
             }
         }
@@ -193,6 +196,17 @@ public class EtcdAccessor {
     }
 
     /**
+     * Creates/updates an entry in etcd without any ttl set.
+     * @see #set(String, String, Integer)
+     * @param key the property key, not null
+     * @param value the value to be set
+     * @return the result map as described above.
+     */
+    public Map<String,String> set(String key, String value){
+        return set(key, value, null);
+    }
+
+    /**
      * Creates/updates an entry in etcd. The response as follows:
      * <pre>
      *     {
@@ -233,18 +247,17 @@ public class EtcdAccessor {
     public Map<String,String> set(String key, String value, Integer ttlSeconds){
         CloseableHttpResponse response = null;
         Map<String,String> result = new HashMap<>();
-        result.put("_" + key +".source", "[etcd]"+serverURL);
         try{
             HttpPut put = new HttpPut(serverURL + "/v2/keys/"+key);
             List<NameValuePair> nvps = new ArrayList<>();
             nvps.add(new BasicNameValuePair("value", value));
             if(ttlSeconds!=null){
-                result.put("ttl", ttlSeconds.toString());
                 nvps.add(new BasicNameValuePair("ttl", ttlSeconds.toString()));
             }
             put.setEntity(new UrlEncodedFormEntity(nvps));
             response = httpclient.execute(put);
-            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_CREATED ||
+                    response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                 HttpEntity entity = response.getEntity();
                 JsonReader reader = readerFactory.createReader(new StringReader(EntityUtils.toString(entity)));
                 JsonObject o = reader.readObject();
@@ -256,14 +269,15 @@ public class EtcdAccessor {
                     result.put("_" + key +".modifiedIndex", String.valueOf(node.getInt("modifiedIndex")));
                 }
                 if(node.containsKey("expiration")) {
-                    result.put("_" + key +".expiration", String.valueOf(node.getInt("expiration")));
+                    result.put("_" + key +".expiration", String.valueOf(node.getString("expiration")));
                 }
-                if(node.containsKey("_" + key +".ttl")) {
+                if(node.containsKey("ttl")) {
                     result.put("_" + key +".ttl", String.valueOf(node.getInt("ttl")));
                 }
-                result.put("value", node.getString("value"));
-                JsonObject prevNode = o.getJsonObject("prevNode");
-                if(prevNode!=null) {
+                result.put(key, node.getString("value"));
+                result.put("_" + key +".source", "[etcd]"+serverURL);
+                if(node.containsKey("prevNode")){
+                    JsonObject prevNode = node.getJsonObject("prevNode");
                     if (prevNode.containsKey("createdIndex")) {
                         result.put("_" + key +".prevNode.createdIndex", String.valueOf(prevNode.getInt("createdIndex")));
                     }
@@ -271,7 +285,7 @@ public class EtcdAccessor {
                         result.put("_" + key +".prevNode.modifiedIndex", String.valueOf(prevNode.getInt("modifiedIndex")));
                     }
                     if(prevNode.containsKey("expiration")) {
-                        result.put("_" + key +".prevNode.expiration", String.valueOf(prevNode.getInt("expiration")));
+                        result.put("_" + key +".prevNode.expiration", String.valueOf(prevNode.getString("expiration")));
                     }
                     if(prevNode.containsKey("ttl")) {
                         result.put("_" + key +".prevNode.ttl", String.valueOf(prevNode.getInt("ttl")));
@@ -281,14 +295,14 @@ public class EtcdAccessor {
                 EntityUtils.consume(entity);
             }
         } catch(Exception e){
-            // TODO log error
-            result.put("_" + key +".error", e.toString());
+            LOG.log(Level.SEVERE, "Error writing to etcd: " + serverURL, e);
+            result.put("_ERROR", "Error writing '"+key+"' to etcd: " + serverURL + ": " + e.toString());
         } finally {
             if(response!=null){
                 try {
                     response.close();
                 } catch (IOException e) {
-                    // TODO log error
+                    LOG.log(Level.WARNING, "Failed to close http response", e);
                 }
             }
         }
@@ -317,8 +331,6 @@ public class EtcdAccessor {
     public Map<String,String> delete(String key){
         CloseableHttpResponse response = null;
         Map<String,String> result = new HashMap<>();
-        result.put("key", key);
-        result.put("_" + key +".source", "[etcd]"+serverURL);
         try{
             HttpDelete delete = new HttpDelete(serverURL + "/v2/keys/"+key);
             List<NameValuePair> nvps = new ArrayList<>();
@@ -336,13 +348,13 @@ public class EtcdAccessor {
                     result.put("_" + key +".modifiedIndex", String.valueOf(node.getInt("modifiedIndex")));
                 }
                 if(node.containsKey("expiration")) {
-                    result.put("_" + key +".expiration", String.valueOf(node.getInt("expiration")));
+                    result.put("_" + key +".expiration", String.valueOf(node.getString("expiration")));
                 }
                 if(node.containsKey("ttl")) {
                     result.put("_" + key +".ttl", String.valueOf(node.getInt("ttl")));
                 }
-                JsonObject prevNode = o.getJsonObject("prevNode");
-                if(prevNode!=null) {
+                if(o.containsKey("prevNode")){
+                    JsonObject prevNode = o.getJsonObject("prevNode");
                     if (prevNode.containsKey("createdIndex")) {
                         result.put("_" + key +".prevNode.createdIndex", String.valueOf(prevNode.getInt("createdIndex")));
                     }
@@ -350,7 +362,7 @@ public class EtcdAccessor {
                         result.put("_" + key +".prevNode.modifiedIndex", String.valueOf(prevNode.getInt("modifiedIndex")));
                     }
                     if(prevNode.containsKey("expiration")) {
-                        result.put("_" + key +".prevNode.expiration", String.valueOf(prevNode.getInt("expiration")));
+                        result.put("_" + key +".prevNode.expiration", String.valueOf(prevNode.getString("expiration")));
                     }
                     if(prevNode.containsKey("ttl")) {
                         result.put("_" + key +".prevNode.ttl", String.valueOf(prevNode.getInt("ttl")));
@@ -360,8 +372,8 @@ public class EtcdAccessor {
                 EntityUtils.consume(entity);
             }
         } catch(Exception e){
-            // TODO log error
-            result.put("_" + key +".error", e.toString());
+            LOG.log(Level.SEVERE, "Error deleting key '"+key+"' from etcd: " + serverURL, e);
+            result.put("_ERROR", "Error deleting '"+key+"' from etcd: " + serverURL + ": " + e.toString());
         } finally {
             if(response!=null){
                 try {
@@ -432,12 +444,10 @@ public class EtcdAccessor {
     public Map<String,String> getProperties(String directory, boolean recursive){
         CloseableHttpResponse response = null;
         Map<String,String> result = new HashMap<>();
-        result.put("_" + directory +".source", "[etcd]"+serverURL);
         try{
             HttpGet get = new HttpGet(serverURL + "/v2/keys/"+directory+"?recursive="+recursive);
             response = httpclient.execute(get);
             if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
-                result.put("_" + directory +".source", "[etcd]"+serverURL);
                 HttpEntity entity = response.getEntity();
                 JsonReader reader = readerFactory.createReader(new StringReader(EntityUtils.toString(entity)));
                 JsonObject o = reader.readObject();
@@ -448,8 +458,8 @@ public class EtcdAccessor {
                 EntityUtils.consume(entity);
             }
         } catch(Exception e){
-            // TODO log error
-            result.put("_" + directory +".error", e.toString());
+            LOG.log(Level.SEVERE, "Error reading properties for '"+directory+"' from etcd: " + serverURL, e);
+            result.put("_ERROR", "Error reading properties for '"+directory+"' from etcd: " + serverURL + ": " + e.toString());
         } finally {
             if(response!=null){
                 try {
@@ -468,7 +478,7 @@ public class EtcdAccessor {
      * @param node
      */
     private void addNodes(Map<String, String> result, JsonObject node) {
-        if(node.getBoolean("dir", false)) {
+        if(!node.containsKey("dir") || "false".equals(node.get("dir"))) {
             String key = node.getString("key").substring(1);
             result.put(key, node.getString("value"));
             if (node.containsKey("createdIndex")) {
@@ -478,14 +488,14 @@ public class EtcdAccessor {
                 result.put("_" + key + ".modifiedIndex", String.valueOf(node.getInt("modifiedIndex")));
             }
             if (node.containsKey("expiration")) {
-                result.put("_" + key + ".expiration", String.valueOf(node.getInt("expiration")));
+                result.put("_" + key + ".expiration", String.valueOf(node.getString("expiration")));
             }
             if (node.containsKey("ttl")) {
                 result.put("_" + key + ".ttl", String.valueOf(node.getInt("ttl")));
             }
             result.put("_" + key +".source", "[etcd]"+serverURL);
         } else {
-            JsonArray nodes = node.getJsonArray("node");
+            JsonArray nodes = node.getJsonArray("nodes");
             if (nodes != null) {
                 for (int i = 0; i < nodes.size(); i++) {
                     addNodes(result, nodes.getJsonObject(i));

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/ba5d9571/modules/integration/etcd/src/main/java/org/apache/tamaya/etcd/EtcdPropertySource.java
----------------------------------------------------------------------
diff --git a/modules/integration/etcd/src/main/java/org/apache/tamaya/etcd/EtcdPropertySource.java b/modules/integration/etcd/src/main/java/org/apache/tamaya/etcd/EtcdPropertySource.java
index 96ebdf3..7d76748 100644
--- a/modules/integration/etcd/src/main/java/org/apache/tamaya/etcd/EtcdPropertySource.java
+++ b/modules/integration/etcd/src/main/java/org/apache/tamaya/etcd/EtcdPropertySource.java
@@ -71,7 +71,25 @@ public class EtcdPropertySource implements PropertySource{
     @Override
     public String get(String key) {
         if(etcdBackend!=null) {
-            return etcdBackend.get(key).get(key);
+            Map<String,String> props = null;
+            if(key.startsWith("_")){
+                String reqKey = key.substring(1);
+                if(reqKey.endsWith(".createdIndex")){
+                    reqKey = reqKey.substring(0,reqKey.length()-".createdIndex".length());
+                } else if(reqKey.endsWith(".modifiedIndex")){
+                    reqKey = reqKey.substring(0,reqKey.length()-".modifiedIndex".length());
+                } else if(reqKey.endsWith(".ttl")){
+                    reqKey = reqKey.substring(0,reqKey.length()-".ttl".length());
+                } else if(reqKey.endsWith(".expiration")){
+                    reqKey = reqKey.substring(0,reqKey.length()-".expiration".length());
+                } else if(reqKey.endsWith(".source")){
+                    reqKey = reqKey.substring(0,reqKey.length()-".source".length());
+                }
+                props = etcdBackend.get(reqKey);
+            } else{
+                props = etcdBackend.get(key);
+            }
+            return props.get(key);
         }
         return null;
     }

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/ba5d9571/modules/integration/etcd/src/test/java/org/apache/tamaya/etcd/EtcdAccessorTest.java
----------------------------------------------------------------------
diff --git a/modules/integration/etcd/src/test/java/org/apache/tamaya/etcd/EtcdAccessorTest.java b/modules/integration/etcd/src/test/java/org/apache/tamaya/etcd/EtcdAccessorTest.java
new file mode 100644
index 0000000..e3b4196
--- /dev/null
+++ b/modules/integration/etcd/src/test/java/org/apache/tamaya/etcd/EtcdAccessorTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.apache.tamaya.etcd;
+
+import org.junit.BeforeClass;
+
+import java.net.MalformedURLException;
+import java.util.Map;
+import java.util.UUID;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests for th etcd backend integration. You must have set a system property so, theses tests are executed, e.g.
+ * {@code -Detcd.url=http://127.0.0.1:4001}.
+ */
+public class EtcdAccessorTest {
+
+    private static EtcdAccessor accessor;
+    static boolean execute = false;
+
+    @BeforeClass
+    public static void setup() throws MalformedURLException {
+        accessor = new EtcdAccessor();
+        if(!accessor.getVersion().contains("etcd")){
+            System.out.println("Disabling etcd tests, etcd not accessible at: " + System.getProperty("etcd.url"));
+            System.out.println("Configure etcd with -Detcd.url=http://<IP>:<PORT>");
+        }
+        else{
+            execute = true;
+        }
+    }
+
+    @org.junit.Test
+    public void testGetVersion() throws Exception {
+        if(!execute)return;
+        assertEquals(accessor.getVersion(), "etcd 0.4.9");
+    }
+
+    @org.junit.Test
+    public void testGet() throws Exception {
+        if(!execute)return;
+        Map<String,String> result = accessor.get("test1");
+        assertNotNull(result);
+    }
+
+    @org.junit.Test
+    public void testSetNormal() throws Exception {
+        if(!execute)return;
+        String value = UUID.randomUUID().toString();
+        Map<String,String> result = accessor.set("testSetNormal", value);
+        assertNull(result.get("_testSetNormal.ttl"));
+        assertEquals(accessor.get("testSetNormal").get("testSetNormal"), value);
+    }
+
+    @org.junit.Test
+    public void testSetNormal2() throws Exception {
+        if(!execute)return;
+        String value = UUID.randomUUID().toString();
+        Map<String,String> result = accessor.set("testSetNormal2", value, null);
+        assertNull(result.get("_testSetNormal2.ttl"));
+        assertEquals(accessor.get("testSetNormal2").get("testSetNormal2"), value);
+    }
+
+    @org.junit.Test
+    public void testSetWithTTL() throws Exception {
+        if(!execute)return;
+        String value = UUID.randomUUID().toString();
+        Map<String,String> result = accessor.set("testSetWithTTL", value, 1);
+        assertNotNull(result.get("_testSetWithTTL.ttl"));
+        assertEquals(accessor.get("testSetWithTTL").get("testSetWithTTL"), value);
+        Thread.sleep(2000L);
+        result = accessor.get("testSetWithTTL");
+        assertNull(result.get("testSetWithTTL"));
+    }
+
+
+    @org.junit.Test
+    public void testDelete() throws Exception {
+        if(!execute)return;
+        String value = UUID.randomUUID().toString();
+        Map<String,String> result = accessor.set("testDelete", value, null);
+        assertEquals(accessor.get("testDelete").get("testDelete"), value);
+        assertNotNull(result.get("_testDelete.createdIndex"));
+        result = accessor.delete("testDelete");
+        assertEquals(result.get("_testDelete.prevNode.value"),value);
+        assertNull(accessor.get("testDelete").get("testDelete"));
+    }
+
+    @org.junit.Test
+    public void testGetProperties() throws Exception {
+        if(!execute)return;
+        String value = UUID.randomUUID().toString();
+        accessor.set("testGetProperties1", value);
+        Map<String,String> result = accessor.getProperties("");
+        assertNotNull(result);
+        assertEquals(result.get("testGetProperties1"), value);
+        assertNotNull(result.get("_testGetProperties1.createdIndex"));
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/ba5d9571/modules/integration/etcd/src/test/java/org/apache/tamaya/etcd/EtcdPropertySourceTest.java
----------------------------------------------------------------------
diff --git a/modules/integration/etcd/src/test/java/org/apache/tamaya/etcd/EtcdPropertySourceTest.java b/modules/integration/etcd/src/test/java/org/apache/tamaya/etcd/EtcdPropertySourceTest.java
new file mode 100644
index 0000000..590dc12
--- /dev/null
+++ b/modules/integration/etcd/src/test/java/org/apache/tamaya/etcd/EtcdPropertySourceTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.apache.tamaya.etcd;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.Map;
+
+import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Created by atsticks on 07.01.16.
+ */
+public class EtcdPropertySourceTest {
+
+    private EtcdPropertySource propertySource = new EtcdPropertySource();
+
+    @BeforeClass
+    public static void setup(){
+        System.setProperty("etcd.url", "http://192.168.99.105:4001");
+    }
+
+    @Test
+    public void testGetOrdinal() throws Exception {
+        assertEquals(propertySource.getOrdinal(), 100);
+    }
+
+    @Test
+    public void testGetDefaultOrdinal() throws Exception {
+        assertEquals(propertySource.getDefaultOrdinal(), 100);
+    }
+
+    @Test
+    public void testGetName() throws Exception {
+        assertEquals("etcd", propertySource.getName());
+    }
+
+    @Test
+    public void testGet() throws Exception {
+        Map<String,String> props = propertySource.getProperties();
+        for(Map.Entry<String,String> en:props.entrySet()){
+            assertNotNull("Key not found: " + en.getKey(), propertySource.get(en.getKey()));
+        }
+    }
+
+    @Test
+    public void testGetProperties() throws Exception {
+        Map<String,String> props = propertySource.getProperties();
+        assertNotNull(props);
+    }
+
+    @Test
+    public void testIsScannable() throws Exception {
+        assertTrue(propertySource.isScannable());
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/ba5d9571/modules/integration/pom.xml
----------------------------------------------------------------------
diff --git a/modules/integration/pom.xml b/modules/integration/pom.xml
index ca0b649..719b1a9 100644
--- a/modules/integration/pom.xml
+++ b/modules/integration/pom.xml
@@ -37,6 +37,7 @@ under the License.
         <module>cdi</module>
         <module>osgi</module>
         <module>camel</module>
+        <module>etcd</module>
     </modules>
 
 </project>
\ No newline at end of file