Merge pull request #234 from jbonofre/KARAF-7020

[KARAF-7020] Upgrade to oshi 5.4.1
diff --git a/appender/kafka/pom.xml b/appender/kafka/pom.xml
index da38378..dcbb360 100644
--- a/appender/kafka/pom.xml
+++ b/appender/kafka/pom.xml
@@ -50,7 +50,7 @@
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
-            <version>2.10.2</version>
+            <version>2.10.5.1</version>
         </dependency>
 
         <!-- test -->
diff --git a/assembly/src/main/feature/feature.xml b/assembly/src/main/feature/feature.xml
index f10865c..8302043 100644
--- a/assembly/src/main/feature/feature.xml
+++ b/assembly/src/main/feature/feature.xml
@@ -133,6 +133,13 @@
         <feature>decanter-collector-soap-core</feature>
     </feature>
 
+    <feature name="decanter-collector-openstack" version="${project.version}" description="Karaf Decanter Openstack collector">
+        <feature>decanter-common</feature>
+        <feature>scheduler</feature>
+        <configfile finalname="/etc/org.apache.karaf.decanter.collector.openstack.cfg">mvn:org.apache.karaf.decanter.collector/org.apache.karaf.decanter.collector.openstack/${project.version}/cfg</configfile>
+        <bundle>mvn:org.apache.karaf.decanter.collector/org.apache.karaf.decanter.collector.openstack/${project.version}</bundle>
+    </feature>
+
     <feature name="decanter-collector-oshi" version="${project.version}" description="Karaf Decanter oshi (system) collector">
         <feature>decanter-common</feature>
         <feature>scheduler</feature>
diff --git a/collector/kafka/pom.xml b/collector/kafka/pom.xml
index 2a3a24c..f4261c1 100644
--- a/collector/kafka/pom.xml
+++ b/collector/kafka/pom.xml
@@ -50,7 +50,7 @@
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
-            <version>2.10.2</version>
+            <version>2.10.5.1</version>
         </dependency>
     </dependencies>
 
diff --git a/collector/openstack/NOTICE b/collector/openstack/NOTICE
new file mode 100644
index 0000000..4e4af9e
--- /dev/null
+++ b/collector/openstack/NOTICE
@@ -0,0 +1,57 @@
+Apache Karaf Decanter

+Copyright 2015-2019 The Apache Software Foundation

+

+I. Included Software

+

+This product includes software developed at

+The Apache Software Foundation (http://www.apache.org/).

+Licensed under the Apache License 2.0.

+

+This product includes software developed at

+Elastic (https://www.elastic.co/).

+Licensed under the Apache License 2.0.

+

+This product includes software developed at

+OrientDB (http://orientdb.com).

+Licensed under the Apache License 2.0.

+

+II. Used Software

+

+This product uses software developed at

+The OSGi Alliance (http://www.osgi.org/).

+Copyright (c) OSGi Alliance (2000, 2010).

+Licensed under the Apache License 2.0.

+

+This product uses software developed at

+OPS4J (http://www.ops4j.org/).

+Licensed under the Apache License 2.0.

+

+This product uses software developed at

+SLF4J (http://www.slf4j.org/).

+Licensed under the MIT License.

+

+This product uses software developed at

+JUnit (http://www.junit.org/).

+Licensed under the Eclipse Public License 1.0.

+

+This product uses software developed at

+Redis (http://www.redis.io).

+Licensed under the BSD license.

+

+This product uses software developed at

+Dropwizard (http://www.dropwizard.io).

+Licensed under the Apache License 2.0.

+

+This product uses software developed at

+searchbox.io (https://github.com/searchbox-io)

+Licensed under the Apache License 2.0.

+

+This product uses software developed at

+MongoDB (https://www.mongodb.com/)

+Licensed under the Apache License 2.0.

+

+III. License Summary

+- Apache License 2.0

+- MIT License

+- Eclipse Public License 1.0

+- BSD License

diff --git a/collector/openstack/pom.xml b/collector/openstack/pom.xml
new file mode 100644
index 0000000..1d80514
--- /dev/null
+++ b/collector/openstack/pom.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        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.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.karaf.decanter</groupId>
+        <artifactId>collector</artifactId>
+        <version>2.7.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <groupId>org.apache.karaf.decanter.collector</groupId>
+    <artifactId>org.apache.karaf.decanter.collector.openstack</artifactId>
+    <packaging>bundle</packaging>
+    <name>Apache Karaf :: Decanter :: Collector :: OpenStack</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.karaf.decanter</groupId>
+            <artifactId>org.apache.karaf.decanter.api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.decanter.collector</groupId>
+            <artifactId>org.apache.karaf.decanter.collector.utils</artifactId>
+        </dependency>
+
+        <!-- test -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.decanter.marshaller</groupId>
+            <artifactId>org.apache.karaf.decanter.marshaller.json</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.johnzon</groupId>
+            <artifactId>johnzon-mapper</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.johnzon</groupId>
+            <artifactId>johnzon-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+                <extensions>true</extensions>
+                <configuration>
+                    <obrRepository>NONE</obrRepository>
+                    <instructions>
+                        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
+                        <Export-Package>!*</Export-Package>
+                        <Import-Package>
+                            *
+                        </Import-Package>
+                        <Private-Package>
+                            org.apache.karaf.decanter.collector.openstack,
+                            org.apache.karaf.decanter.collector.utils
+                        </Private-Package>
+                        <_dsannotations>*</_dsannotations>
+                    </instructions>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>attach-artifact</goal>
+                        </goals>
+                        <configuration>
+                            <artifacts>
+                                <artifact>
+                                    <file>src/main/cfg/org.apache.karaf.decanter.collector.openstack.cfg</file>
+                                    <type>cfg</type>
+                                </artifact>
+                            </artifacts>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/collector/openstack/src/main/cfg/org.apache.karaf.decanter.collector.openstack.cfg b/collector/openstack/src/main/cfg/org.apache.karaf.decanter.collector.openstack.cfg
new file mode 100644
index 0000000..944e88a
--- /dev/null
+++ b/collector/openstack/src/main/cfg/org.apache.karaf.decanter.collector.openstack.cfg
@@ -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.
+#
+################################################################################
+
+#
+# Decanter Openstack collector
+#
+# Openstack services API locations
+#
+
+#openstack.identity=http://localhost/identity
+#openstack.project=2c7be0bac05c4144a328d4ab3dfac379
+#openstack.username=admin
+#openstack.password=secret
+#openstack.domain=default
+
+#openstack.compute.enabled=true
+#openstack.compute=http://localhost/compute/v2.1
+#openstack.block.storage.enabled=true
+#openstack.block.storage=http://localhost/volume/v3
+#openstack.image.enabled=true
+#openstack.image=http://localhost/image
+#openstack.metric.enabled=true
+#openstack.metric=http://localhost/metric
+
+# Unmarshaller to use
+unmarshaller.target=(dataFormat=json)
diff --git a/collector/openstack/src/main/java/org/apache/karaf/decanter/collector/openstack/OpenstackCollector.java b/collector/openstack/src/main/java/org/apache/karaf/decanter/collector/openstack/OpenstackCollector.java
new file mode 100644
index 0000000..e02a027
--- /dev/null
+++ b/collector/openstack/src/main/java/org/apache/karaf/decanter/collector/openstack/OpenstackCollector.java
@@ -0,0 +1,620 @@
+/*
+ * 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.karaf.decanter.collector.openstack;
+
+import org.apache.karaf.decanter.api.marshaller.Unmarshaller;
+import org.apache.karaf.decanter.collector.utils.PropertiesPreparator;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Component(
+        service = Runnable.class,
+        name = "org.apache.karaf.decanter.collector.openstack",
+        immediate = true,
+        property = {
+                "decanter.collector.name=openstack",
+                "scheduler.period:Long=300",
+                "scheduler.concurrent:Boolean=false",
+                "scheduler.name=decanter-collector-openstack"
+        }
+)
+public class OpenstackCollector implements Runnable {
+
+    @Reference
+    public EventAdmin dispatcher;
+
+    @Reference
+    public Unmarshaller unmarshaller;
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(OpenstackCollector.class);
+
+    private Dictionary<String, Object> config;
+    private String topic;
+
+    private URL identity;
+    private String username;
+    private String password;
+    private String domain;
+    private String project;
+
+    private boolean computeEnabled = true;
+    private String compute = null;
+    private boolean computeUsageFlatten = true;
+    private boolean blockStorageEnabled = true;
+    private String blockStorage = null;
+    private boolean imageEnabled = true;
+    private String image = null;
+    private boolean metricEnabled = true;
+    private String metric = null;
+
+    @Activate
+    public void activate(ComponentContext componentContext) throws Exception {
+        activate(componentContext.getProperties());
+    }
+
+    public void activate(Dictionary<String, Object> config) throws Exception {
+        this.config = config;
+        if (config.get("topic") != null) {
+            topic = (String) config.get("topic");
+        } else {
+            topic = "decanter/collect/openstack";
+        }
+        if (config.get("openstack.identity") == null) {
+            throw new IllegalStateException("openstack.identity is not configured");
+        }
+        identity = new URL(config.get("openstack.identity") + "/v3/auth/tokens");
+        if (config.get("openstack.username") == null) {
+            throw new IllegalStateException("openstack.username is not configured");
+        }
+        username = (String) config.get("openstack.username");
+        if (config.get("openstack.password") == null) {
+            throw new IllegalStateException("openstack.password is not configured");
+        }
+        password = (String) config.get("openstack.password");
+        if (config.get("openstack.domain") == null) {
+            throw new IllegalStateException("openstack.domain is not configured");
+        }
+        domain = (String) config.get("openstack.domain");
+        if (config.get("openstack.project") == null) {
+            throw new IllegalStateException("openstack.project is not configured");
+        }
+        project = (String) config.get("openstack.project");
+
+        if (config.get("openstack.compute.enabled") != null) {
+            computeEnabled = Boolean.parseBoolean((String) config.get("openstack.compute.enabled"));
+        }
+        compute = (String) config.get("openstack.compute");
+        if (config.get("openstack.compute.usage.flatten") != null) {
+            computeUsageFlatten = Boolean.parseBoolean((String) config.get("openstack.compute.usage.flatten"));
+        }
+
+        if (config.get("openstack.block.storage.enabled") != null) {
+            blockStorageEnabled = Boolean.parseBoolean((String) config.get("openstack.block.storage.enabled"));
+        }
+        blockStorage = (String) config.get("openstack.block.storage");
+
+        if (config.get("openstack.image.enabled") != null) {
+            imageEnabled = Boolean.parseBoolean((String) config.get("openstack.image.enabled"));
+        }
+        image = (String) config.get("openstack.image");
+
+        if (config.get("openstack.metric.enabled") != null) {
+            metricEnabled = Boolean.parseBoolean((String) config.get("openstack.metric.enabled"));
+        }
+        metric = (String) config.get("openstack.metric");
+    }
+
+    @Override
+    public void run() {
+        try {
+            String token = auth();
+
+            if (computeEnabled) {
+                try {
+                    computeUsage(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get compute usage", e);
+                }
+                try {
+                    computeServers(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get compute servers", e);
+                }
+                try {
+                    computeFlavors(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get compute flavors", e);
+                }
+                try {
+                    computeOsKeypairs(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get compute OS keypairs", e);
+                }
+                try {
+                    computeLimits(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get compute limits", e);
+                }
+                try {
+                    computeOsAggregates(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get compute OS aggregates", e);
+                }
+                try {
+                    computeAvailabilityZones(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get compute availability zones", e);
+                }
+                try {
+                    computeOsHypervisors(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get compute OS hypervisors", e);
+                }
+                try {
+                    computeOsInstanceUsageAuditLog(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get compute OS instance usage audit log", e);
+                }
+                try {
+                    computeOsMigrations(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get compute OS migrations", e);
+                }
+                try {
+                    computeOsServerGroups(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get compute OS server groups", e);
+                }
+                try {
+                    computeOsServices(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get compute OS services", e);
+                }
+            }
+            if (blockStorageEnabled) {
+                try {
+                    blockStorageVolumeTypes(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get block storage volume types", e);
+                }
+                try {
+                    blockStorageVolumesDetail(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get block storage volumes detail", e);
+                }
+                try {
+                    blockStorageManageableVolumes(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get block storage manageable volumes", e);
+                }
+                try {
+                    blockStorageSnapshotsDetail(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get block storage snapshots detail", e);
+                }
+                try {
+                    blockStorageVolumeTransfers(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get block storage volume transfers", e);
+                }
+                try {
+                    blockStorageAttachments(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get block storage attachments", e);
+                }
+                try {
+                    blockStorageBackups(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get block storage backups", e);
+                }
+                try {
+                    blockStorageOsServices(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get block storage OS services", e);
+                }
+                try {
+                    blockStorageGroups(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get block storage groups", e);
+                }
+                try {
+                    blockStorageGroupSnapshots(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get block storage group snapshots", e);
+                }
+                try {
+                    blockStorageGroupTypes(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get block storage group types", e);
+                }
+                try {
+                    blockStorageOsHosts(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get block storage OS hosts", e);
+                }
+                try {
+                    blockStorageLimits(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get block storage limits", e);
+                }
+                try {
+                    blockStorageResourceFilters(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get block storage resource filters", e);
+                }
+                try {
+                    blockStorageQosSpecs(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get block storage QoS specs", e);
+                }
+            }
+            if (imageEnabled) {
+                try {
+                    images(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get images", e);
+                }
+                try {
+                    imageStore(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get image store", e);
+                }
+                try {
+                    imageTasks(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get image tasks", e);
+                }
+            }
+            if (metricEnabled) {
+                try {
+                    metric(token, dispatcher);
+                } catch (Exception e) {
+                    LOGGER.warn("Can't get metric", e);
+                }
+            }
+
+        } catch (Exception e) {
+            LOGGER.warn("Can't get openstack details", e);
+        }
+    }
+
+    protected void computeUsage(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(compute + "/os-simple-tenant-usage", token);
+
+        if (computeUsageFlatten) {
+            String extract = response.substring(response.indexOf("[") +1, response.indexOf("]"));
+            String[] split = extract.split("}, \\{");
+            for (int i = 0; i < split.length; i++) {
+                Map<String, Object> data = new HashMap<>();
+                String use = split[i];
+                if (!use.startsWith("{")) {
+                    use = "{" + use;
+                }
+                if (!use.endsWith("}")) {
+                    use = use + "}";
+                }
+                data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(use.getBytes())));
+                PropertiesPreparator.prepare(data, config);
+                dispatcher.postEvent(new Event(topic, data));
+            }
+        } else {
+            Map<String, Object> data = new HashMap<>();
+            data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+            PropertiesPreparator.prepare(data, config);
+            dispatcher.postEvent(new Event(topic, data));
+        }
+    }
+
+    protected void computeServers(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(compute + "/servers/detail", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void computeFlavors(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(compute + "/flavors/detail", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void computeOsKeypairs(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(compute + "/os-keypairs", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void computeLimits(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(compute + "/limits", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void computeOsAggregates(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(compute + "/os-aggregates", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void computeAvailabilityZones(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(compute + "/os-availability-zone/detail", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void computeOsHypervisors(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(compute + "/os-hypervisors/detail", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void computeOsInstanceUsageAuditLog(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(compute + "/os-instance_usage_audit_log", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void computeOsMigrations(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(compute + "/os-migrations", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void computeOsServerGroups(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(compute + "/os-server-groups", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void computeOsServices(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(compute + "/os-services", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void blockStorageVolumeTypes(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(blockStorage + "/" + project + "/types", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void blockStorageVolumesDetail(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(blockStorage + "/" + project + "/volumes/detail", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void blockStorageManageableVolumes(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(blockStorage + "/" + project + "/manageable_volumes/detail", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void blockStorageSnapshotsDetail(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(blockStorage + "/" + project + "/snapshots/detail", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void blockStorageVolumeTransfers(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(blockStorage + "/" + project + "/volume-transfers/detail", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void blockStorageAttachments(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(blockStorage + "/" + project + "/attachments/detail", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void blockStorageBackups(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(blockStorage + "/" + project + "/backups/detail", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void blockStorageOsServices(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(blockStorage + "/" + project + "/os-services", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void blockStorageGroups(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(blockStorage + "/" + project + "/groups", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void blockStorageGroupSnapshots(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(blockStorage + "/" + project + "/group_snapshots/detail", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void blockStorageGroupTypes(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(blockStorage + "/" + project + "/group_types", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void blockStorageOsHosts(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(blockStorage + "/" + project + "/os-hosts", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void blockStorageLimits(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(blockStorage + "/" + project + "/limits", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void blockStorageResourceFilters(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(blockStorage + "/" + project + "/resource_filters", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void blockStorageQosSpecs(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(blockStorage + "/" + project + "/qos-specs", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void images(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(image + "/v2/images", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void imageStore(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(image + "/v2/info/stores", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void imageTasks(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(image + "/v2/tasks", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    protected void metric(String token, EventAdmin dispatcher) throws Exception {
+        String response = request(metric + "/v1/metric", token);
+        Map<String, Object> data = new HashMap<>();
+        data.putAll(unmarshaller.unmarshal(new ByteArrayInputStream(response.getBytes())));
+        PropertiesPreparator.prepare(data, config);
+        dispatcher.postEvent(new Event(topic, data));
+    }
+
+    private String request(String url, String token) throws Exception {
+        HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
+        connection.setRequestMethod("GET");
+        connection.setRequestProperty("X-Auth-Token", token);
+        connection.setDoInput(true);
+        if (connection.getResponseCode() != 200) {
+            throw new IllegalStateException("Can't get data from " + url + " (" + connection.getResponseCode() + "): " + connection.getResponseMessage());
+        }
+        StringBuilder response = new StringBuilder();
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                response.append(line).append("\n");
+            }
+        }
+        return response.toString();
+    }
+
+    protected String auth() throws Exception {
+        LOGGER.debug("Authentication on {}", identity);
+        HttpURLConnection connection = (HttpURLConnection) identity.openConnection();
+        connection.setRequestMethod("POST");
+        connection.setRequestProperty("Content-Type", "application/json");
+        connection.setDoInput(true);
+        connection.setDoOutput(true);
+        String authJson = "{ \"auth\": { \"identity\": { \"methods\": [\"password\"], \"password\": { \"user\": { \"name\": \"" + username + "\", \"domain\": { \"name\": \"" + domain + "\" }, \"password\": \"" + password + "\" }}}, \"scope\": { \"project\": { \"id\": \"" + project + "\" }}}}";
+        LOGGER.debug("Authentication JSON request:");
+        LOGGER.debug(authJson);
+        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream()))) {
+            writer.write(authJson);
+            writer.flush();
+        }
+        if (connection.getResponseCode() != 201) {
+            throw new IllegalStateException("Can't get token (" + connection.getResponseCode() + "): " + connection.getResponseMessage());
+        }
+        return connection.getHeaderField("X-Subject-Token");
+    }
+
+    /**
+     * For testing purpose.
+     */
+    public void setDispatcher(EventAdmin dispatcher) {
+        this.dispatcher = dispatcher;
+    }
+
+}
diff --git a/collector/pom.xml b/collector/pom.xml
index 698b9bd..a2dd1a9 100644
--- a/collector/pom.xml
+++ b/collector/pom.xml
@@ -49,6 +49,7 @@
         <module>log</module>
         <module>log4j-socket</module>
         <module>mqtt</module>
+        <module>openstack</module>
         <module>oshi</module>
         <module>redis</module>
         <module>rest</module>
diff --git a/manual/src/main/asciidoc/user-guide/collectors.adoc b/manual/src/main/asciidoc/user-guide/collectors.adoc
index efbee18..8cd1c47 100644
--- a/manual/src/main/asciidoc/user-guide/collectors.adoc
+++ b/manual/src/main/asciidoc/user-guide/collectors.adoc
@@ -1198,6 +1198,46 @@
 
 Decanter Druid collector schedules the queries and send the results in the dispatcher.
 
+==== OpenStack
+
+Karaf Decanter OpenStack collector periodically request OpenStack API to get details about openstack ecosystem.
+
+The `karaf-collector-openstack` feature installs the OpenStack collector:
+
+----
+karaf@root()> feature:install decanter-collector-openstack
+----
+
+This feature also installs `etc/org.apache.karaf.decanter.collector.openstack.cfg` configuration file:
+
+----
+#
+# Decanter Openstack collector
+#
+# Openstack services API locations
+#
+
+#openstack.identity=http://localhost/identity
+#openstack.project=2c7be0bac05c4144a328d4ab3dfac379
+#openstack.username=admin
+#openstack.password=secret
+#openstack.domain=default
+
+#openstack.compute.enabled=true
+#openstack.compute=http://localhost/compute/v2.1
+#openstack.block.storage.enabled=true
+#openstack.block.storage=http://localhost/volume/v3
+#openstack.image.enabled=true
+#openstack.image=http://localhost/image
+#openstack.metric.enabled=true
+#openstack.metric=http://localhost/metric
+
+# Unmarshaller to use
+unmarshaller.target=(dataFormat=json)
+----
+
+You have to define the locations of the OpenStack APIs and if you enabled requesting the APIs or not.
+
 ==== Customizing properties in collectors
 
 You can add, rename or remove properties collected by the collectors before sending it to the dispatcher.
diff --git a/pom.xml b/pom.xml
index 7814a7d..de5da2c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,7 +42,7 @@
         <cassandra.driver.version>4.9.0</cassandra.driver.version>
         <glassfish-json.version>1.1.4</glassfish-json.version>
         <json-api.version>1.1.4</json-api.version>
-        <kafka.version>2.6.0</kafka.version>
+        <kafka.version>2.7.0</kafka.version>
         <karaf.version>4.2.10</karaf.version>
         <kibana.version>3.1.1</kibana.version>
         <kibana4.version>4.1.2</kibana4.version>
@@ -358,12 +358,12 @@
             <dependency>
                 <groupId>org.apache.johnzon</groupId>
                 <artifactId>johnzon-mapper</artifactId>
-                <version>1.2.8</version>
+                <version>1.2.10</version>
             </dependency>
             <dependency>
                 <groupId>org.apache.johnzon</groupId>
                 <artifactId>johnzon-core</artifactId>
-                <version>1.2.8</version>
+                <version>1.2.10</version>
             </dependency>
             <dependency>
                 <groupId>org.apache.derby</groupId>