Merge pull request #31 from tcpcloud/stacklight

Stacklight
diff --git a/collectd/_common.sls b/collectd/_common.sls
new file mode 100644
index 0000000..7725520
--- /dev/null
+++ b/collectd/_common.sls
@@ -0,0 +1,34 @@
+{%- from "collectd/map.jinja" import client with context %}
+
+{%- if grains.os == 'Ubuntu' and (grains.osrelease in ['10.04', '12.04']) %}
+
+collectd_repo:
+  pkgrepo.managed:
+  - human_name: Collectd
+  - ppa: nikicat/collectd
+  - file: /etc/apt/sources.list.d/collectd.list
+  - require_in:
+    - pkg: collectd_client_packages
+
+collectd_amqp_packages:
+  pkg.installed:
+  - names:
+    - librabbitmq0
+
+{%- endif %}
+
+collectd_client_packages:
+  pkg.installed:
+  - names: {{ client.pkgs }}
+
+
+/usr/lib/collectd-python:
+  file.recurse:
+  - source: salt://collectd/files/plugin
+
+collectd_client_grains_dir:
+  file.directory:
+  - name: /etc/salt/grains.d
+  - mode: 700
+  - makedirs: true
+  - user: root
diff --git a/collectd/_service.sls b/collectd/_service.sls
new file mode 100644
index 0000000..cef78e2
--- /dev/null
+++ b/collectd/_service.sls
@@ -0,0 +1,145 @@
+{%- if client.enabled %}
+
+{{ client.service }}_client_conf_dir:
+  file.directory:
+  - name: {{ client.config_dir }}
+  - user: root
+  - mode: 750
+  - makedirs: true
+
+{{ client.service }}_client_conf_dir_clean:
+  file.directory:
+  - name: {{ client.config_dir }}
+  - clean: true
+
+{%- for plugin_name, plugin in plugins.iteritems() %}
+
+{%- if plugin.get('plugin', 'native') not in ['python'] %}
+
+{{ client.config_dir }}/{{ plugin_name }}.conf:
+  file.managed:
+  {%- if plugin.template is defined %}
+  - source: salt://{{ plugin.template }}
+  - template: jinja
+  - defaults:
+    plugin: {{ plugin|yaml }}
+  {%- else %}
+  - contents: "<LoadPlugin {{ plugin.plugin }}>\n  Globals false\n</LoadPlugin>\n"
+  {%- endif %}
+  - user: root
+  - mode: 660
+  - require:
+    - file: {{ client.service }}_client_conf_dir
+  - require_in:
+    - file: {{ client.service }}_client_conf_dir_clean
+
+{%- endif %}
+
+{%- endfor %}
+
+{%- if client.file_logging %}
+
+{{ client.config_dir }}/00_collectd_logfile.conf:
+  file.managed:
+  - source: salt://collectd/files/collectd_logfile.conf
+  - template: jinja
+  - defaults:
+    service_name: {{ client.service }}
+  - user: root
+  - group: root
+  - mode: 660
+  - require:
+    - file: {{ client.service }}_client_conf_dir
+  - require_in:
+    - file: {{ client.service }}_client_conf_dir_clean
+
+{%- endif %}
+
+{{ client.config_dir }}/collectd_python.conf:
+  file.managed:
+  - source: salt://collectd/files/collectd_python.conf
+  - template: jinja
+  - user: root
+  - group: root
+  - mode: 660
+  - defaults:
+      plugin: {{ plugins|yaml }}
+  - require:
+    - file: {{ client.service }}_client_conf_dir
+  - require_in:
+    - file: {{ client.service }}_client_conf_dir_clean
+
+{{ client.config_file }}:
+  file.managed:
+  - source: salt://collectd/files/collectd.conf
+  - template: jinja
+  - user: root
+  - group: root
+  - mode: 640
+  - defaults:
+    plugin: {{ plugins|yaml }}
+    client: {{ client|yaml }}
+  - require:
+    - file: {{ client.service }}_client_conf_dir
+  - require_in:
+    - file: {{ client.service }}_client_conf_dir_clean
+
+{%- set network_backend = {} %}
+{%- for backend_name, backend in client.backend.iteritems() %}
+
+{%- if backend.engine not in ['network'] %}
+
+{{ client.config_dir }}/collectd_writer_{{ backend_name }}.conf:
+  file.managed:
+  - source: salt://collectd/files/backend/{{ backend.engine }}.conf
+  - template: jinja
+  - user: root
+  - group: root
+  - mode: 660
+  - defaults:
+    backend: {{ backend|yaml }}
+  - require:
+    - file: {{ client.service }}_client_conf_dir
+  - require_in:
+    - file: {{ client.service }}_client_conf_dir_clean
+
+{%- else %}
+
+{%- set network_backend = salt['grains.filter_by']({'default': network_backend}, merge={backend_name: backend}) %}
+
+{%- endif %}
+
+{%- endfor %}
+
+{%- if network_backend|length > 0 %}
+
+{{ client.config_dir }}/collectd_writer_network.conf:
+  file.managed:
+  - source: salt://collectd/files/backend/network.conf
+  - template: jinja
+  - user: root
+  - group: root
+  - mode: 660
+  - defaults:
+    backend: {{ backend|yaml }}
+  - require:
+    - file: {{ client.service }}_client_conf_dir
+  - require_in:
+    - file: {{ client.service }}_client_conf_dir_clean
+
+{%- endif %}
+
+
+{{ client.service }}_service:
+{%- if client.automatic_starting %}
+  service.running:
+  - enable: true
+  - watch:
+    - file: {{ client.config_file }}
+    - file: {{ client.config_dir }}/*
+{%- else %}
+  service.disabled:
+{%- endif %}
+  - name: {{ client.service }}
+
+{%- endif %}
diff --git a/collectd/client.sls b/collectd/client.sls
index d81c660..eca7071 100644
--- a/collectd/client.sls
+++ b/collectd/client.sls
@@ -1,26 +1,8 @@
 {%- from "collectd/map.jinja" import client with context %}
 {%- if client.enabled %}
 
-{%- if grains.os == 'Ubuntu' and (grains.osrelease in ['10.04', '12.04']) %}
-
-collectd_repo:
-  pkgrepo.managed:
-  - human_name: Collectd
-  - ppa: nikicat/collectd
-  - file: /etc/apt/sources.list.d/collectd.list
-  - require_in:
-    - pkg: collectd_client_packages
-
-collectd_amqp_packages:
-  pkg.installed:
-  - names: 
-    - librabbitmq0
-
-{%- endif %}
-
-collectd_client_packages:
-  pkg.installed:
-  - names: {{ client.pkgs }}
+include:
+- collectd._common
 
 /etc/collectd:
   file.directory:
@@ -30,38 +12,19 @@
   - require:
     - pkg: collectd_client_packages
 
-collectd_client_conf_dir:
-  file.directory:
-  - name: {{ client.config_dir }}
-  - user: root
-  - mode: 750
-  - makedirs: true
-  - require:
-    - pkg: collectd_client_packages
+{%- set service_grains = {'collectd': {'remote_plugin': {}, 'local_plugin': {}}} %}
 
-collectd_client_conf_dir_clean:
-  file.directory:
-  - name: {{ client.config_dir }}
-  - clean: true
-
-collectd_client_grains_dir:
-  file.directory:
-  - name: /etc/salt/grains.d
-  - mode: 700
-  - makedirs: true
-  - user: root
-
-/usr/lib/collectd-python:
-  file.recurse:
-  - source: salt://collectd/files/plugin
-
-{%- set service_grains = {'collectd': {'plugin': {}}} %}
 {%- for service_name, service in pillar.items() %}
 {%- if service.get('_support', {}).get('collectd', {}).get('enabled', False) %}
+
 {%- set grains_fragment_file = service_name+'/meta/collectd.yml' %}
-{%- macro load_grains_file() %}{% include grains_fragment_file %}{% endmacro %}
+{%- macro load_grains_file() %}{% include grains_fragment_file ignore missing %}{% endmacro %}
 {%- set grains_yaml = load_grains_file()|load_yaml %}
-{%- set _dummy = service_grains.collectd.plugin.update(grains_yaml.plugin) %}
+
+{%- if grains_yaml is mapping %}
+{%- set service_grains = salt['grains.filter_by']({'default': service_grains}, merge={'collectd': grains_yaml}) %}
+{%- endif %}
+
 {%- endif %}
 {%- endfor %}
 
@@ -86,135 +49,7 @@
   - watch:
     - file: collectd_client_grain
 
-{%- for plugin_name, plugin in service_grains.collectd.get('plugin', {}).iteritems() %}
-
-{%- if (plugin.get('execution', 'local') == 'local' or client.remote_collector) and plugin.get('plugin', 'native') not in ['python'] %}
-
-{{ client.config_dir }}/{{ plugin_name }}.conf:
-  file.managed:
-  {%- if plugin.template is defined %}
-  - source: salt://{{ plugin.template }}
-  - template: jinja
-  - defaults:
-    plugin: {{ plugin|yaml }}
-  {%- else %}
-  - contents: "<LoadPlugin {{ plugin.plugin }}>\n  Globals false\n</LoadPlugin>\n"
-  {%- endif %}
-  - user: root
-  - mode: 660
-  - require:
-    - file: collectd_client_conf_dir
-  - require_in:
-    - file: collectd_client_conf_dir_clean
-  - watch_in:
-    - service: collectd_service
-
-{%- endif %}
-
-{%- endfor %}
-
-{%- if client.file_logging %}
-
-/etc/collectd/conf.d/00_collectd_logfile.conf:
-  file.managed:
-  - source: salt://collectd/files/collectd_logfile.conf
-  - user: root
-  - group: root
-  - mode: 660
-  - watch_in:
-    - service: collectd_service
-  - require:
-    - file: collectd_client_conf_dir
-  - require_in:
-    - file: collectd_client_conf_dir_clean
-
-{%- endif %}
-
-/etc/collectd/conf.d/collectd_python.conf:
-  file.managed:
-  - source: salt://collectd/files/collectd_python.conf
-  - template: jinja
-  - user: root
-  - group: root
-  - mode: 660
-  - defaults:
-      plugin: {{ service_grains.collectd.plugin|yaml }}
-  - watch_in:
-    - service: collectd_service
-  - require:
-    - file: collectd_client_conf_dir
-  - require_in:
-    - file: collectd_client_conf_dir_clean
-
-/etc/collectd/filters.conf:
-  file.managed:
-  - source: salt://collectd/files/filters.conf
-  - template: jinja
-  - user: root
-  - group: root
-  - mode: 660
-  - watch_in:
-    - service: collectd_service
-  - require:
-    - file: collectd_client_conf_dir
-  - require_in:
-    - file: collectd_client_conf_dir_clean
-
-/etc/collectd/thresholds.conf:
-  file.managed:
-  - source: salt://collectd/files/thresholds.conf
-  - template: jinja
-  - user: root
-  - group: root
-  - mode: 660
-  - watch_in:
-    - service: collectd_service
-  - require:
-    - file: collectd_client_conf_dir
-  - require_in:
-    - file: collectd_client_conf_dir_clean
-
-{{ client.config_file }}:
-  file.managed:
-  - source: salt://collectd/files/collectd.conf
-  - template: jinja
-  - user: root
-  - group: root
-  - mode: 640
-  - defaults:
-    service_grains: {{ service_grains|yaml }}
-  - require:
-    - file: collectd_client_conf_dir
-  - require_in:
-    - file: collectd_client_conf_dir_clean
-  - watch_in:
-    - service: collectd_service
-
-{%- for backend_name, backend in client.backend.iteritems() %}
-
-{{ client.config_dir }}/collectd_writer_{{ backend_name }}.conf:
-  file.managed:
-  - source: salt://collectd/files/backend/{{ backend.engine }}.conf
-  - template: jinja
-  - user: root
-  - group: root
-  - mode: 660
-  - defaults:
-    backend_name: "{{ backend_name }}"
-  - require:
-    - file: collectd_client_conf_dir
-  - require_in:
-    - file: collectd_client_conf_dir_clean
-  - watch_in:
-    - service: collectd_service
-
-{%- endfor %}
-
-collectd_service:
-  service.running:
-  - name: collectd
-  - enable: true
-  - require:
-    - pkg: collectd_client_packages
+{%- set plugins = service_grains.collectd.local_plugin %}
+{%- include "collectd/_service.sls" %}
 
 {%- endif %}
diff --git a/collectd/files/backend/amqp.conf b/collectd/files/backend/amqp.conf
index 606f446..ed2f968 100644
--- a/collectd/files/backend/amqp.conf
+++ b/collectd/files/backend/amqp.conf
@@ -1,4 +1,3 @@
-{%- set backend = salt['pillar.get']('collectd:client:backend:'+backend_name) %}
 <LoadPlugin amqp>
   Globals false
 </LoadPlugin>
diff --git a/collectd/files/backend/carbon.conf b/collectd/files/backend/carbon.conf
index d39f4a4..7758f97 100644
--- a/collectd/files/backend/carbon.conf
+++ b/collectd/files/backend/carbon.conf
@@ -1,4 +1,3 @@
-{%- set backend = salt['pillar.get']('collectd:client:backend:'+backend_name) %}
 <LoadPlugin write_graphite>
   Globals false
 </LoadPlugin>
diff --git a/collectd/files/backend/http.conf b/collectd/files/backend/http.conf
index 66401be..752df40 100644
--- a/collectd/files/backend/http.conf
+++ b/collectd/files/backend/http.conf
@@ -1,4 +1,3 @@
-{%- set backend = salt['pillar.get']('collectd:client:backend:'+backend_name) %}
 <LoadPlugin write_http>
   Globals false
 </LoadPlugin>
diff --git a/collectd/files/backend/network.conf b/collectd/files/backend/network.conf
index 08fcd63..e97c530 100644
--- a/collectd/files/backend/network.conf
+++ b/collectd/files/backend/network.conf
@@ -1,9 +1,8 @@
-{%- from "collectd/map.jinja" import client with context %}
 <LoadPlugin network>
   Globals false
 </LoadPlugin>
 
-{%- for backend_name in client.backend.iteritems() %}
+{%- for _, backend in client.backend.iteritems() %}
 <Plugin network>
 	{%- if backend.mode == 'client' %}
 	Server "{{ backend.host }}" "{{ backend.port }}"
diff --git a/collectd/files/collectd.conf b/collectd/files/collectd.conf
index 03175e2..d815eb1 100644
--- a/collectd/files/collectd.conf
+++ b/collectd/files/collectd.conf
@@ -1,4 +1,3 @@
-{%- from "collectd/map.jinja" import client with context %}
 {%- from "linux/map.jinja" import system with context %}
 
 # Config file for collectd(1).
@@ -15,7 +14,11 @@
 # Global settings for the daemon.                                            #
 ##############################################################################
 
+{%- if client.use_fqdn %}
 Hostname "{{ system.name }}.{{ system.domain }}"
+{%- else %}
+Hostname "{{ system.name }}"
+{%- endif %}
 FQDNLookup false
 
 #BaseDir "/var/lib/collectd"
@@ -42,10 +45,12 @@
 # accessed.                                                                  #
 ##############################################################################
 
+{%- if client.syslog_logging %}
 LoadPlugin syslog
 <Plugin syslog>
 	LogLevel info
 </Plugin>
+{%- endif %}
 
 ##############################################################################
 # LoadPlugin section                                                         #
@@ -879,10 +884,10 @@
 #</Plugin>
 
 {%- if client.file_logging %}
-Include "/etc/collectd/conf.d/00_collectd_logfile.conf"
+Include "{{ client.config_dir }}/00_collectd_logfile.conf"
 {%- endif %}
-{%- for plugin_name, plugin in service_grains.collectd.get('plugin', {}).iteritems() %}
-{%- if (plugin.get('execution', 'local') == 'local' or client.remote_collector) and plugin.get('plugin', 'native') not in ['python'] %}
+{%- for plugin_name, plugin in plugin.iteritems() %}
+{%- if plugin.get('plugin', 'native') not in ['python'] %}
 Include "{{ client.config_dir }}/{{ plugin_name }}.conf"
 {%- endif %}
 {%- endfor %}
@@ -890,6 +895,3 @@
 {%- for backend_name, backend in client.backend.iteritems() %}
 Include "{{ client.config_dir }}/collectd_writer_{{ backend_name }}.conf"
 {%- endfor %}
-
-Include "/etc/collectd/filters.conf"
-Include "/etc/collectd/thresholds.conf"
diff --git a/collectd/files/collectd_check_local_endpoint.conf b/collectd/files/collectd_check_local_endpoint.conf
index 4144e96..b154564 100644
--- a/collectd/files/collectd_check_local_endpoint.conf
+++ b/collectd/files/collectd_check_local_endpoint.conf
@@ -1,6 +1,4 @@
-
 {%- if plugin.get('endpoint', {})|length > 0 %}
-
 Import "check_local_endpoint"
 
 <Module "check_local_endpoint">
@@ -13,5 +11,4 @@
   Url "{{ endpoint_name }}" "{{ endpoint.url }}"
   {%- endfor %}
 </Module>
-
-{%- endif %}
\ No newline at end of file
+{%- endif %}
diff --git a/collectd/files/collectd_logfile.conf b/collectd/files/collectd_logfile.conf
index 55bfbff..6d4fb6e 100644
--- a/collectd/files/collectd_logfile.conf
+++ b/collectd/files/collectd_logfile.conf
@@ -4,7 +4,7 @@
 
 <Plugin logfile>
   LogLevel warning
-  File "/var/log/collectd.log"
+  File "/var/log/{{ service_name }}.log"
   Timestamp true
   PrintSeverity false
 </Plugin>
diff --git a/collectd/files/collectd_python.conf b/collectd/files/collectd_python.conf
index a30ee36..53ad6f4 100644
--- a/collectd/files/collectd_python.conf
+++ b/collectd/files/collectd_python.conf
@@ -1,4 +1,3 @@
-{%- from "collectd/map.jinja" import client with context %}
 <LoadPlugin python>
   Globals false
 </LoadPlugin>
@@ -7,12 +6,12 @@
   ModulePath "/usr/lib/collectd-python"
   LogTraces false
   Interactive false
-  {%- if plugin is mapping %}
+
   {%- for plugin_name, plugin in plugin.iteritems() %}
-  {%- if (plugin.get('execution', 'local') == 'local' or client.remote_collector) and plugin.get('plugin', 'native') == 'python' %}
-  {%- include plugin.template %}
+  {%- if plugin.get('plugin', 'native') == 'python' %}
+  {% include plugin.template %}
   {%- endif %}
+
   {%- endfor %}
-  {%- endif %}
 
 </Plugin>
diff --git a/collectd/files/collectd_systemd.service b/collectd/files/collectd_systemd.service
new file mode 100644
index 0000000..ddb0a62
--- /dev/null
+++ b/collectd/files/collectd_systemd.service
@@ -0,0 +1,22 @@
+[Unit]
+Description=Statistics collection and monitoring daemon
+After=local-fs.target network.target
+Requires=local-fs.target network.target
+ConditionPathExists={{ config_file }}
+Documentation=man:collectd(1)
+Documentation=man:collectd.conf(5)
+Documentation=https://collectd.org
+
+[Service]
+Type=notify
+NotifyAccess=main
+EnvironmentFile=-/etc/default/{{ service_name }}
+ExecStartPre=/usr/sbin/collectd -t -C {{ config_file }}
+ExecStart=/usr/sbin/collectd -C {{ config_file }}
+Restart=always
+RestartSec=10
+
+{%- if automatic_starting %}
+[Install]
+WantedBy=multi-user.target
+{%- endif %}
diff --git a/collectd/files/collectd_upstart.service b/collectd/files/collectd_upstart.service
new file mode 100644
index 0000000..f728999
--- /dev/null
+++ b/collectd/files/collectd_upstart.service
@@ -0,0 +1,12 @@
+# {{ service_name }}
+
+description     "{{ service_name }}"
+
+{%- if automatic_starting %}
+start on runlevel [2345]
+stop on runlevel [!2345]
+{%- endif %}
+
+respawn
+
+exec /usr/bin/collectd -f -C {{ config_file }}
diff --git a/collectd/files/plugin/collectd_apache_check.py b/collectd/files/plugin/collectd_apache_check.py
index 63ef855..790a60f 100644
--- a/collectd/files/plugin/collectd_apache_check.py
+++ b/collectd/files/plugin/collectd_apache_check.py
@@ -50,10 +50,6 @@
 plugin = ApacheCheckPlugin(collectd)
 
 
-def init_callback():
-    plugin.restore_sigchld()
-
-
 def config_callback(conf):
     plugin.config_callback(conf)
 
@@ -61,6 +57,5 @@
 def read_callback():
     plugin.read_callback()
 
-collectd.register_init(init_callback)
 collectd.register_config(config_callback)
 collectd.register_read(read_callback)
diff --git a/collectd/files/plugin/collectd_base.py b/collectd/files/plugin/collectd_base.py
index f042628..28cea12 100644
--- a/collectd/files/plugin/collectd_base.py
+++ b/collectd/files/plugin/collectd_base.py
@@ -17,7 +17,6 @@
 import json
 import signal
 import subprocess
-import sys
 import time
 import traceback
 
@@ -169,8 +168,8 @@
 
             ("foobar\n", "")
 
-            None if the command couldn't be executed or returned a non-zero
-            status code
+            (None, None) if the command couldn't be executed or returned a
+            non-zero status code
         """
         start_time = time.time()
         try:
@@ -186,14 +185,14 @@
         except Exception as e:
             self.logger.error("Cannot execute command '%s': %s : %s" %
                               (cmd, str(e), traceback.format_exc()))
-            return None
+            return (None, None)
 
         returncode = proc.returncode
 
         if returncode != 0:
             self.logger.error("Command '%s' failed (return code %d): %s" %
                               (cmd, returncode, stderr))
-            return None
+            return (None, None)
         if self.debug:
             elapsedtime = time.time() - start_time
             self.logger.info("Command '%s' returned %s in %0.3fs" %
@@ -222,18 +221,16 @@
 
     @staticmethod
     def restore_sigchld():
-        """Restores the SIGCHLD handler for Python <= v2.6.
+        """Restores the SIGCHLD handler.
 
         This should be provided to collectd as the init callback by plugins
-        that execute external programs.
+        that execute external programs and want to check the return code.
 
         Note that it will BREAK the exec plugin!!!
 
-        See https://github.com/deniszh/collectd-iostat-python/issues/2 for
-        details.
+        See contrib/python/getsigchld.py in the collectd project for details.
         """
-        if sys.version_info[0] == 2 and sys.version_info[1] <= 6:
-            signal.signal(signal.SIGCHLD, signal.SIG_DFL)
+        signal.signal(signal.SIGCHLD, signal.SIG_DFL)
 
     def notification_callback(self, notification):
         if not self.depends_on_resource:
diff --git a/collectd/files/plugin/collectd_contrail_apis.py b/collectd/files/plugin/collectd_contrail_apis.py
new file mode 100644
index 0000000..ad2260e
--- /dev/null
+++ b/collectd/files/plugin/collectd_contrail_apis.py
@@ -0,0 +1,134 @@
+#!/usr/bin/python
+#
+# Copyright 2016 Mirantis, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import collectd
+import requests
+import xml.dom.minidom
+import xml
+
+import collectd_base as base
+
+
+NAME = 'contrail'
+# Default sampling interval
+INTERVAL = 60
+
+
+def check_state(item, state):
+    return item.getElementsByTagName(
+        "state")[0].childNodes[0].toxml() == state
+
+
+class ContrailApiPlugin(base.Base):
+
+    def __init__(self, *args, **kwargs):
+        super(ContrailApiPlugin, self).__init__(*args, **kwargs)
+        self.plugin = NAME
+        self.session = requests.Session()
+        self.session.mount(
+            'http://',
+            requests.adapters.HTTPAdapter(max_retries=self.max_retries)
+        )
+        self.session.mount(
+            'https://',
+            requests.adapters.HTTPAdapter(max_retries=self.max_retries)
+        )
+        self.urls = {}
+        self.xml_element = {}
+        self.result_type = {}
+        self.state = {}
+
+    def config_callback(self, config):
+        super(ContrailApiPlugin, self).config_callback(config)
+        for node in config.children:
+            self.logger.debug("Got config request for '{}': {} {}".format(
+                node.key.lower(), node.values[0], node.values[1])
+            )
+            if node.key.lower() == "url":
+                self.urls[node.values[0]] = node.values[1]
+            elif node.key.lower() == 'xmlelement':
+                self.xml_element[node.values[0]] = node.values[1]
+            elif node.key.lower() == 'resulttype':
+                self.result_type[node.values[0]] = node.values[1]
+            elif node.key.lower() == 'state':
+                self.state[node.values[0]] = node.values[1]
+
+    def itermetrics(self):
+        for name, url in self.urls.items():
+            self.logger.debug("Requesting {} URL {}".format(
+                name, url)
+            )
+            try:
+                r = self.session.get(url, timeout=self.timeout)
+            except Exception as e:
+                msg = "Got exception for '{}': {}".format(name, e)
+                raise base.CheckException(msg)
+            else:
+                if r.status_code != 200:
+                    self.logger.error(
+                        ("{} ({}) responded with code {} "
+                         "").format(name, url,
+                                    r.status_code))
+                    yield {'type_instance': name, 'values': self.FAIL}
+                else:
+                    try:
+                        self.logger.debug(
+                            "Got response from {}: '{}'"
+                            "".format(url, r.text))
+                        px = xml.dom.minidom.parseString(r.text)
+                        itemlist = px.getElementsByTagName(
+                            self.xml_element[name]
+                        )
+                        if name not in self.result_type:
+                            count = 0
+                            state = self.state.get('name')
+                            for i in itemlist:
+                                if state is None or check_state(i, state):
+                                    count = count + 1
+                            self.logger.debug(
+                                "Got count for {}: '{}'".format(name, count))
+                            yield {'type_instance': name, 'values': count}
+                        else:
+                            rval = itemlist[0].getElementsByTagName(
+                                self.result_type[name]
+                            )[0].childNodes[0].toxml()
+                        self.logger.debug(
+                            "Got val for {}: '{}'".format(name, rval))
+                        yield {'type_instance': name, 'values': rval}
+                    except Exception as e:
+                        msg = ("Got exception while parsing "
+                               "response for '{}': {}").format(name, e)
+                        raise base.CheckException(msg)
+
+
+plugin = ContrailApiPlugin(collectd)
+
+
+def config_callback(conf):
+    plugin.config_callback(conf)
+
+
+def notification_callback(notification):
+    plugin.notification_callback(notification)
+
+
+def read_callback():
+    plugin.conditional_read_callback()
+
+collectd.register_config(config_callback)
+collectd.register_notification(notification_callback)
+collectd.register_read(read_callback, INTERVAL)
diff --git a/collectd/files/plugin/collectd_glusterfs.py b/collectd/files/plugin/collectd_glusterfs.py
new file mode 100644
index 0000000..fbcfc1f
--- /dev/null
+++ b/collectd/files/plugin/collectd_glusterfs.py
@@ -0,0 +1,222 @@
+#!/usr/bin/python
+# Copyright 2016 Mirantis, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import collectd
+import re
+
+import collectd_base as base
+
+NAME = 'glusterfs'
+GLUSTER_BINARY = '/usr/sbin/gluster'
+
+peer_re = re.compile(r'^Hostname: (?P<peer>.+)$', re.MULTILINE)
+state_re = re.compile(r'^State: (?P<state>.+)$', re.MULTILINE)
+
+vol_status_re = re.compile(r'\n\s*\n', re.MULTILINE)
+vol_block_re = re.compile(r'^-+', re.MULTILINE)
+volume_re = re.compile(r'^Status of volume:\s+(?P<volume>.+)', re.MULTILINE)
+brick_server_re = re.compile(r'^Brick\s*:\s*Brick\s*(?P<peer>[^:]+)',
+                             re.MULTILINE)
+disk_free_re = re.compile(
+    r'^Disk Space Free\s*:\s+(?P<disk_free>[.\d]+)(?P<unit>\S+)',
+    re.MULTILINE)
+disk_total_re = re.compile(
+    r'^Total Disk Space\s*:\s+(?P<disk_total>[.\d]+)(?P<unit>\S+)',
+    re.MULTILINE)
+inode_free_re = re.compile(r'^Free Inodes\s*:\s+(?P<inode_free>\d+)',
+                           re.MULTILINE)
+inode_count_re = re.compile(r'^Inode Count\s*:\s+(?P<inode_count>\d+)',
+                            re.MULTILINE)
+
+
+def convert_to_bytes(v, unit):
+    try:
+        i = ('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB').index(unit)
+    except ValueError:
+        i = 1
+    return float(v) * (1024 ** i)
+
+
+class GlusterfsPlugin(base.Base):
+
+    def __init__(self, *args, **kwargs):
+        super(GlusterfsPlugin, self).__init__(*args, **kwargs)
+        self.plugin = NAME
+
+    def itermetrics(self):
+        # Collect peers' metrics
+        out, err = self.execute([GLUSTER_BINARY, 'peer', 'status'],
+                                shell=False)
+        if not out:
+            raise base.CheckException("Failed to execute 'gluster peer'")
+
+        total = 0
+        total_by_state = {
+            'up': 0,
+            'down': 0
+        }
+
+        for line in out.split('\n\n'):
+            peer_m = peer_re.search(line)
+            state_m = state_re.search(line)
+            if peer_m and state_m:
+                total += 1
+                if state_m.group('state') == 'Peer in Cluster (Connected)':
+                    v = 1
+                    total_by_state['up'] += 1
+                else:
+                    v = 0
+                    total_by_state['down'] += 1
+                yield {
+                    'type_instance': 'peer_state',
+                    'values': v,
+                    'meta': {
+                        'peer': peer_m.group('peer')
+                    }
+                }
+
+        for state, count in total_by_state.items():
+            yield {
+                'type_instance': 'peers_count',
+                'values': count,
+                'meta': {
+                    'state': state
+                }
+            }
+            yield {
+                'type_instance': 'peers_percent',
+                'values': 100.0 * count / total,
+                'meta': {
+                    'state': state
+                }
+            }
+
+        # Collect volumes' metrics
+        out, err = self.execute(
+            [GLUSTER_BINARY, 'volume', 'status', 'all', 'detail'],
+            shell=False)
+        if not out:
+            raise base.CheckException("Failed to execute 'gluster volume'")
+
+        for vol_block in vol_status_re.split(out):
+            volume_m = volume_re.search(vol_block)
+            if not volume_m:
+                continue
+            volume = volume_m.group('volume')
+            for line in vol_block_re.split(vol_block):
+                peer_m = brick_server_re.search(line)
+                if not peer_m:
+                    continue
+                volume = volume_m.group('volume')
+                peer = peer_m.group('peer')
+                disk_free_m = disk_free_re.search(line)
+                disk_total_m = disk_total_re.search(line)
+                inode_free_m = inode_free_re.search(line)
+                inode_count_m = inode_count_re.search(line)
+                if disk_free_m and disk_total_m:
+                    free = convert_to_bytes(
+                        disk_free_m.group('disk_free'),
+                        disk_free_m.group('unit'))
+                    total = convert_to_bytes(
+                        disk_total_m.group('disk_total'),
+                        disk_total_m.group('unit'))
+                    used = total - free
+                    yield {
+                        'type_instance': 'space_free',
+                        'values': free,
+                        'meta': {
+                            'volume': volume,
+                            'peer': peer,
+                        }
+                    }
+                    yield {
+                        'type_instance': 'space_percent_free',
+                        'values': free * 100.0 / total,
+                        'meta': {
+                            'volume': volume,
+                            'peer': peer,
+                        }
+                    }
+                    yield {
+                        'type_instance': 'space_used',
+                        'values': used,
+                        'meta': {
+                            'volume': volume,
+                            'peer': peer,
+                        }
+                    }
+                    yield {
+                        'type_instance': 'space_percent_used',
+                        'values': used * 100.0 / total,
+                        'meta': {
+                            'volume': volume,
+                            'peer': peer,
+                        }
+                    }
+                if inode_free_m and inode_count_m:
+                    free = int(inode_free_m.group('inode_free'))
+                    total = int(inode_count_m.group('inode_count'))
+                    used = total - free
+                    yield {
+                        'type_instance': 'inodes_free',
+                        'values': free,
+                        'meta': {
+                            'volume': volume,
+                            'peer': peer,
+                        }
+                    }
+                    yield {
+                        'type_instance': 'inodes_percent_free',
+                        'values': free * 100.0 / total,
+                        'meta': {
+                            'volume': volume,
+                            'peer': peer,
+                        }
+                    }
+                    yield {
+                        'type_instance': 'inodes_used',
+                        'values': used,
+                        'meta': {
+                            'volume': volume,
+                            'peer': peer,
+                        }
+                    }
+                    yield {
+                        'type_instance': 'inodes_percent_used',
+                        'values': used * 100.0 / total,
+                        'meta': {
+                            'volume': volume,
+                            'peer': peer,
+                        }
+                    }
+
+
+plugin = GlusterfsPlugin(collectd)
+
+
+def init_callback():
+    plugin.restore_sigchld()
+
+
+def config_callback(conf):
+    plugin.config_callback(conf)
+
+
+def read_callback():
+    plugin.read_callback()
+
+collectd.register_init(init_callback)
+collectd.register_config(config_callback)
+collectd.register_read(read_callback)
diff --git a/collectd/files/plugin/collectd_libvirt_check.py b/collectd/files/plugin/collectd_libvirt_check.py
index 4660609..d0df216 100644
--- a/collectd/files/plugin/collectd_libvirt_check.py
+++ b/collectd/files/plugin/collectd_libvirt_check.py
@@ -49,10 +49,6 @@
 plugin = LibvirtCheckPlugin(collectd)
 
 
-def init_callback():
-    plugin.restore_sigchld()
-
-
 def config_callback(conf):
     plugin.config_callback(conf)
 
@@ -60,6 +56,5 @@
 def read_callback():
     plugin.read_callback()
 
-collectd.register_init(init_callback)
 collectd.register_config(config_callback)
 collectd.register_read(read_callback)
diff --git a/collectd/files/plugin/collectd_memcached_check.py b/collectd/files/plugin/collectd_memcached_check.py
index fb44aeb..5d0dd26 100644
--- a/collectd/files/plugin/collectd_memcached_check.py
+++ b/collectd/files/plugin/collectd_memcached_check.py
@@ -60,10 +60,6 @@
 plugin = MemcachedCheckPlugin(collectd)
 
 
-def init_callback():
-    plugin.restore_sigchld()
-
-
 def config_callback(conf):
     plugin.config_callback(conf)
 
@@ -71,6 +67,5 @@
 def read_callback():
     plugin.read_callback()
 
-collectd.register_init(init_callback)
 collectd.register_config(config_callback)
 collectd.register_read(read_callback)
diff --git a/collectd/files/plugin/collectd_mysql_check.py b/collectd/files/plugin/collectd_mysql_check.py
index a42414c..3f59896 100644
--- a/collectd/files/plugin/collectd_mysql_check.py
+++ b/collectd/files/plugin/collectd_mysql_check.py
@@ -103,10 +103,6 @@
 plugin = MySQLCheckPlugin(collectd)
 
 
-def init_callback():
-    plugin.restore_sigchld()
-
-
 def config_callback(conf):
     plugin.config_callback(conf)
 
@@ -114,6 +110,5 @@
 def read_callback():
     plugin.read_callback()
 
-collectd.register_init(init_callback)
 collectd.register_config(config_callback)
 collectd.register_read(read_callback)
diff --git a/collectd/files/plugin/collectd_nginx_check.py b/collectd/files/plugin/collectd_nginx_check.py
new file mode 100644
index 0000000..9b8210e
--- /dev/null
+++ b/collectd/files/plugin/collectd_nginx_check.py
@@ -0,0 +1,62 @@
+#!/usr/bin/python
+# Copyright 2016 Mirantis, Inc.
+#
+# Licensed under the nginx 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.nginx.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.
+
+import collectd
+import collectd_base as base
+import requests
+
+NAME = 'nginx'
+
+
+class NginxCheckPlugin(base.Base):
+
+    def __init__(self, *args, **kwargs):
+        super(NginxCheckPlugin, self).__init__(*args, **kwargs)
+        self.plugin = NAME
+        self.url = None
+
+    def config_callback(self, conf):
+        super(NginxCheckPlugin, self).config_callback(conf)
+
+        for node in conf.children:
+            if node.key == 'Url':
+                self.url = node.values[0]
+                break
+
+        if self.url is None:
+            self.logger.error("{}: Missing Url parameter".format(NAME))
+
+    def read_callback(self):
+        try:
+            requests.get(self.url, timeout=self.timeout)
+            self.dispatch_check_metric(self.OK)
+        except Exception as err:
+            msg = "{}: Failed to check service: {}".format(NAME, err)
+            self.logger.error(msg)
+            self.dispatch_check_metric(self.FAIL, msg)
+
+
+plugin = NginxCheckPlugin(collectd)
+
+
+def config_callback(conf):
+    plugin.config_callback(conf)
+
+
+def read_callback():
+    plugin.read_callback()
+
+collectd.register_config(config_callback)
+collectd.register_read(read_callback)
diff --git a/collectd/files/plugin/collectd_vrrp.py b/collectd/files/plugin/collectd_vrrp.py
new file mode 100644
index 0000000..b020ec2
--- /dev/null
+++ b/collectd/files/plugin/collectd_vrrp.py
@@ -0,0 +1,84 @@
+#!/usr/bin/python
+# Copyright 2016 Mirantis, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import collectd
+
+import collectd_base as base
+
+from pyroute2 import IPRoute
+
+NAME = 'vrrp'
+
+
+class VrrpPlugin(base.Base):
+
+    def __init__(self, *args, **kwargs):
+        super(VrrpPlugin, self).__init__(*args, **kwargs)
+        self.plugin = NAME
+        self.ip_addresses = []
+        self.ipr = IPRoute()
+
+    def config_callback(self, conf):
+        """Parse the plugin configuration.
+
+        Example:
+
+        <Module "collectd_vrrp">
+            <IPAddress>
+                address "172.16.10.254"
+                label "Foo"
+            </IPAddress>
+            <IPAddress>
+                address "172.16.10.253"
+            </IPAddress>
+        </Module>
+        """
+        super(VrrpPlugin, self).config_callback(conf)
+
+        for node in conf.children:
+            if node.key == 'IPAddress':
+                item = {}
+                for child_node in node.children:
+                    if child_node.key not in ('address', 'label'):
+                        continue
+                    item[child_node.key] = child_node.values[0]
+                if 'address' not in item:
+                    self.logger.error("vrrp: Missing 'address' parameter")
+                self.ip_addresses.append(item)
+
+        if len(self.ip_addresses) == 0:
+            self.logger.error("vrrp: Missing 'IPAddress' parameter")
+
+    def itermetrics(self):
+        for ip_address in self.ip_addresses:
+            v = 1 if self.ipr.get_addr(address=ip_address['address']) else 0
+            data = {'values': v, 'meta': {'ip_address': ip_address['address']}}
+            if 'label' in ip_address:
+                data['meta']['label'] = ip_address['label']
+            yield data
+
+
+plugin = VrrpPlugin(collectd)
+
+
+def config_callback(conf):
+    plugin.config_callback(conf)
+
+
+def read_callback():
+    plugin.read_callback()
+
+collectd.register_config(config_callback)
+collectd.register_read(read_callback)
diff --git a/collectd/files/plugin/contrail_ifmap_elements_count.py b/collectd/files/plugin/contrail_ifmap_elements_count.py
new file mode 100644
index 0000000..a9831d2
--- /dev/null
+++ b/collectd/files/plugin/contrail_ifmap_elements_count.py
@@ -0,0 +1,79 @@
+#!/usr/bin/python
+#
+#    Copyright 2016 Mirantis, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+#
+
+import time
+import signal
+import string
+import subprocess
+import sys
+
+plugin_name = "contrail"
+plugin_instance = "ifmap-elements-count"
+plugin_interval = 90
+plugin_type = 'gauge'
+
+command = "/usr/bin/ifmap-view visual visual 2>&1 | wc -l"
+
+
+def restore_sigchld():
+    signal.signal(signal.SIGCHLD, signal.SIG_DFL)
+
+
+def log_verbose(msg):
+    collectd.info('%s plugin [verbose]: %s' % (plugin_name, msg))
+
+
+def payload():
+    ifmap_view_number_of_elements = subprocess.check_output(
+        command, shell=True)
+    return ifmap_view_number_of_elements
+
+
+def configure_callback(conf):
+    for node in conf.children:
+        val = str(node.values[0])
+
+
+def payload_callback():
+    value = payload()
+    # log_verbose(
+    #     'Sending value: %s.%s=%s' % (
+    #         plugin_name, '-'.join([val.plugin, val.type]), value))
+    val = collectd.Values(
+        plugin=plugin_name,  # metric source
+        plugin_instance=plugin_instance,
+        type=plugin_type,
+        type_instance=plugin_name,
+        interval=plugin_interval,
+        meta={'0': True},
+        values=[value]
+    )
+
+    val.dispatch()
+
+
+if __name__ == '__main__':
+    print "Plugin: " + plugin_name
+    payload = payload()
+    print("%s" % (payload))
+    sys.exit(0)
+else:
+    import collectd
+
+    collectd.register_init(restore_sigchld)
+    collectd.register_config(configure_callback)
+    collectd.register_read(payload_callback, plugin_interval)
diff --git a/collectd/files/plugin/elasticsearch_cluster.py b/collectd/files/plugin/elasticsearch_cluster.py
index c3dcf37..e08d08a 100644
--- a/collectd/files/plugin/elasticsearch_cluster.py
+++ b/collectd/files/plugin/elasticsearch_cluster.py
@@ -35,6 +35,7 @@
         self.plugin = NAME
         self.address = '127.0.0.1'
         self.port = 9200
+        self._node_id = None
         self.session = requests.Session()
         self.url = None
         self.session.mount(
@@ -51,25 +52,41 @@
             if node.key == 'Port':
                 self.port = node.values[0]
 
-        self.url = "http://{address}:{port}/_cluster/health".format(
+        self.url = "http://{address}:{port}/".format(
             **{
                 'address': self.address,
                 'port': int(self.port),
             })
 
-    def itermetrics(self):
+    def query_api(self, resource):
+        url = "{}{}".format(self.url, resource)
         try:
-            r = self.session.get(self.url)
+            r = self.session.get(url)
         except Exception as e:
-            msg = "Got exception for '{}': {}".format(self.url, e)
+            msg = "Got exception for '{}': {}".format(url, e)
             raise base.CheckException(msg)
 
         if r.status_code != 200:
-            msg = "{} responded with code {}".format(
-                self.url, r.status_code)
+            msg = "{} responded with code {}".format(url, r.status_code)
             raise base.CheckException(msg)
 
-        data = r.json()
+        return r.json()
+
+    @property
+    def node_id(self):
+        if self._node_id is None:
+            local_node = self.query_api('_nodes/_local')
+            self._node_id = local_node.get('nodes', {}).keys()[0]
+
+        return self._node_id
+
+    def itermetrics(self):
+        # Collect cluster metrics only from the elected master
+        master_node = self.query_api('_cluster/state/master_node')
+        if master_node.get('master_node', '') != self.node_id:
+            return
+
+        data = self.query_api('_cluster/health')
         self.logger.debug("Got response from Elasticsearch: '%s'" % data)
 
         yield {
@@ -92,10 +109,6 @@
 plugin = ElasticsearchClusterHealthPlugin(collectd, 'elasticsearch')
 
 
-def init_callback():
-    plugin.restore_sigchld()
-
-
 def config_callback(conf):
     plugin.config_callback(conf)
 
@@ -103,6 +116,5 @@
 def read_callback():
     plugin.read_callback()
 
-collectd.register_init(init_callback)
 collectd.register_config(config_callback)
 collectd.register_read(read_callback)
diff --git a/collectd/files/plugin/haproxy.py b/collectd/files/plugin/haproxy.py
index 17514e2..2299d6e 100644
--- a/collectd/files/plugin/haproxy.py
+++ b/collectd/files/plugin/haproxy.py
@@ -248,6 +248,7 @@
             # NOLB/MAINT/MAINT(via)...
             if status in STATUS_MAP:
                 backend_server_states[pxname][status] += 1
+                backend_server_states[pxname]['_count'] += 1
                 # Emit metric for the backend server
                 yield {
                     'type_instance': 'backend_server',
@@ -261,9 +262,24 @@
 
         for pxname, states in backend_server_states.iteritems():
             for s in STATUS_MAP.keys():
+                val = states.get(s, 0)
                 yield {
                     'type_instance': 'backend_servers',
-                    'values': states.get(s, 0),
+                    'values': val,
+                    'meta': {
+                        'backend': pxname,
+                        'state': s.lower()
+                    }
+                }
+
+                if backend_server_states[pxname]['_count'] == 0:
+                    prct = 0
+                else:
+                    prct = (100.0 * val) / \
+                        backend_server_states[pxname]['_count']
+                yield {
+                    'type_instance': 'backend_servers_percent',
+                    'values': prct,
                     'meta': {
                         'backend': pxname,
                         'state': s.lower()
@@ -291,10 +307,6 @@
 plugin = HAProxyPlugin(collectd)
 
 
-def init_callback():
-    plugin.restore_sigchld()
-
-
 def config_callback(conf):
     plugin.config_callback(conf)
 
@@ -302,6 +314,5 @@
 def read_callback():
     plugin.read_callback()
 
-collectd.register_init(init_callback)
 collectd.register_config(config_callback)
 collectd.register_read(read_callback)
diff --git a/collectd/files/plugin/influxdb.py b/collectd/files/plugin/influxdb.py
index 43cd82d..4b7426b 100644
--- a/collectd/files/plugin/influxdb.py
+++ b/collectd/files/plugin/influxdb.py
@@ -129,10 +129,6 @@
 plugin = InfluxDBClusterPlugin(collectd)
 
 
-def init_callback():
-    plugin.restore_sigchld()
-
-
 def config_callback(conf):
     plugin.config_callback(conf)
 
@@ -140,6 +136,5 @@
 def read_callback():
     plugin.read_callback()
 
-collectd.register_init(init_callback)
 collectd.register_config(config_callback)
 collectd.register_read(read_callback)
diff --git a/collectd/files/plugin/rabbitmq_info.py b/collectd/files/plugin/rabbitmq_info.py
index c92ce0e..d78b6cb 100644
--- a/collectd/files/plugin/rabbitmq_info.py
+++ b/collectd/files/plugin/rabbitmq_info.py
@@ -79,14 +79,14 @@
                 self.api_overview_url, r.status_code)
             raise base.CheckException(msg)
 
-        objects = overview['object_totals']
-        stats['queues'] = objects['queues']
-        stats['consumers'] = objects['consumers']
-        stats['connections'] = objects['connections']
-        stats['exchanges'] = objects['exchanges']
-        stats['channels'] = objects['channels']
-        stats['messages'] = overview['queue_totals']['messages']
-        stats['running_nodes'] = len(overview['contexts'])
+        objects = overview.get('object_totals', {})
+        stats['queues'] = objects.get('queues', 0)
+        stats['consumers'] = objects.get('consumers', 0)
+        stats['connections'] = objects.get('connections', 0)
+        stats['exchanges'] = objects.get('exchanges', 0)
+        stats['channels'] = objects.get('channels', 0)
+        stats['messages'] = overview.get('queue_totals', {}).get('messages', 0)
+        stats['running_nodes'] = len(overview.get('contexts', []))
 
         for k, v in stats.iteritems():
             yield {'type_instance': k, 'values': v}
diff --git a/collectd/init.sls b/collectd/init.sls
index b794478..febb675 100644
--- a/collectd/init.sls
+++ b/collectd/init.sls
@@ -1,4 +1,9 @@
 {%- if pillar.collectd is defined %}
 include:
+{%- if pillar.collectd.client is defined %}
 - collectd.client
-{%- endif %}
\ No newline at end of file
+{%- endif %}
+{%- if pillar.collectd.remote_client is defined %}
+- collectd.remote_client
+{%- endif %}
+{%- endif %}
diff --git a/collectd/map.jinja b/collectd/map.jinja
index a0156c5..ace3a0b 100644
--- a/collectd/map.jinja
+++ b/collectd/map.jinja
@@ -7,16 +7,20 @@
         'config_dir': '/etc/collectd.d',
         'read_interval': 60,
         'file_logging': True,
-        'remote_collector': False
+        'syslog_logging': True,
+        'use_fqdn': True,
+        'automatic_starting': True,
     },
     'Debian': {
-        'pkgs': ['collectd-core', 'snmp', 'python-yaml', 'libpython2.7'],
+        'pkgs': ['collectd-core', 'snmp', 'python-yaml', 'libpython2.7', 'python-simplejson'],
         'service': 'collectd',
         'config_file': '/etc/collectd/collectd.conf',
         'config_dir': '/etc/collectd/conf.d',
         'read_interval': 60,
         'file_logging': True,
-        'remote_collector': False
+        'syslog_logging': True,
+        'use_fqdn': True,
+        'automatic_starting': True,
     },
     'RedHat': {
         'pkgs': ['collectd', 'collectd-ping', 'net-snmp', 'PyYAML'],
@@ -25,6 +29,21 @@
         'config_dir': '/etc/collectd.d',
         'read_interval': 60,
         'file_logging': True,
-        'remote_collector': False
+        'syslog_logging': True,
+        'use_fqdn': True,
+        'automatic_starting': True,
     },
 }, merge=salt['pillar.get']('collectd:client')) %}
+
+{% set remote_client = salt['grains.filter_by']({
+    'default': {
+        'service': 'remote_collectd',
+        'config_file': '/etc/remote_collectd/collectd.conf',
+        'config_dir': '/etc/remote_collectd/conf.d',
+        'read_interval': 60,
+        'file_logging': True,
+        'syslog_logging': False,
+        'use_fqdn': True,
+        'automatic_starting': True,
+    }
+}, merge=salt['pillar.get']('collectd:remote_client')) %}
diff --git a/collectd/meta/collectd.yml b/collectd/meta/collectd.yml
index 98d6ee1..dc349b5 100644
--- a/collectd/meta/collectd.yml
+++ b/collectd/meta/collectd.yml
@@ -1,30 +1,33 @@
-plugin:
+{%- from "collectd/map.jinja" import client with context %}
+{%- from "collectd/map.jinja" import remote_client with context %}
+local_plugin:
   collectd_processes:
     plugin: processes
-    execution: local
     template: collectd/files/collectd_processes.conf
     process:
-      collectdmon: {}
-  collectd_check_local_endpoint:
-    plugin: python
-    execution: local
-    template: collectd/files/collectd_check_local_endpoint.conf
-    endpoint: {}
-{%- if pillar.collectd.client.get('check', {}).curl is defined %}
+  {%- if pillar.collectd.get('client', {}).get('enabled', False) %}
+      collectd:
+        match: '(collectd.*{{ client.config_file }}|collectd$)'
+  {%- endif %}
+  {%- if pillar.collectd.get('remote_client', {}).get('enabled', False) %}
+      remote_collectd:
+        match: 'collectd.*{{ remote_client.config_file }}'
+  {%- endif %}
+  {%- if pillar.collectd.get('client', {}).get('check', {}).curl is defined %}
   collectd_curl:
     plugin: curl
     execution: local
     template: collectd/files/collectd_curl.conf
     data: {{ pillar.collectd.client.check.curl|yaml }}
-{%- endif %}
-{%- if pillar.collectd.client.get('check', {}).ping is defined %}
+  {%- endif %}
+  {%- if pillar.collectd.get('client', {}).get('check', {}).ping is defined %}
   collectd_ping:
     plugin: ping
     execution: local
     template: collectd/files/collectd_ping.conf
     data: {{ pillar.collectd.client.check.ping|yaml }}
-{%- endif %}
-{%- if pillar.get('external', {}).network_device is defined %}
+  {%- endif %}
+  {%- if pillar.get('external', {}).network_device is defined %}
   collectd_network_device:
     plugin: snmp
     execution: local
@@ -45,4 +48,9 @@
         - 1.3.6.1.2.1.31.1.1.1.7
         - 1.3.6.1.2.1.31.1.1.1.11
     host: {{ pillar.external.network_device|yaml }}
-{%- endif %}
\ No newline at end of file
+  {%- endif %}
+  collectd_check_local_endpoint:
+    plugin: python
+    execution: remote
+    template: collectd/files/collectd_check_local_endpoint.conf
+    endpoint: {}
diff --git a/collectd/meta/sphinx.yml b/collectd/meta/sphinx.yml
index 4959fff..e7fa0cb 100644
--- a/collectd/meta/sphinx.yml
+++ b/collectd/meta/sphinx.yml
@@ -1,11 +1,11 @@
 {%- from "collectd/map.jinja" import client with context %}
-{%- set service_grains = {'collectd': {'plugin': {}}} %}
+{%- set service_grains = {'collectd': {'local_plugin': {}}} %}
 {%- for service_name, service in pillar.items() %}
 {%- if service.get('_support', {}).get('collectd', {}).get('enabled', False) %}
 {%- set grains_fragment_file = service_name+'/meta/collectd.yml' %}
 {%- macro load_grains_file() %}{% include grains_fragment_file %}{% endmacro %}
 {%- set grains_yaml = load_grains_file()|load_yaml %}
-{%- set _dummy = service_grains.collectd.plugin.update(grains_yaml.plugin) %}
+{%- do service_grains.collectd.local_plugin.update(grains_yaml.get('local_plugin', {})) %}
 {%- endif %}
 {%- endfor %}
 doc:
@@ -21,4 +21,4 @@
           value: {{ backend.host }}:{{ backend.port }}
 {%- endfor %}
         plugins:
-          value: {% for plugin_name, plugin in service_grains.collectd.plugin.iteritems() %} {{ plugin.plugin }}{% endfor %}
+          value: {% for plugin_name, plugin in service_grains.collectd.local_plugin.iteritems() %} {{ plugin_name }}{% endfor %}
diff --git a/collectd/remote_client.sls b/collectd/remote_client.sls
new file mode 100644
index 0000000..a7ffcc3
--- /dev/null
+++ b/collectd/remote_client.sls
@@ -0,0 +1,37 @@
+{%- from "collectd/map.jinja" import remote_client with context %}
+{%- if remote_client.enabled %}
+
+include:
+- collectd._common
+
+{# Collect all remote plugins from Salt mine #}
+{%- set plugins = {} %}
+{%- for node_name, node_grains in salt['mine.get']('*', 'grains.items').iteritems() %}
+{%- if node_grains.collectd is defined %}
+{%- set plugins = salt['grains.filter_by']({'default': plugins}, merge=node_grains.collectd.get('remote_plugin', {})) %}
+{%- endif %}
+{%- endfor %}
+
+{%- set client = remote_client %}
+{%- include "collectd/_service.sls" %}
+
+{{ remote_client.service }}_service_file:
+  file.managed:
+{%- if grains.get('init', None) == 'systemd' %}
+  - name: /etc/systemd/system/{{ remote_client.service }}.service
+  - source: salt://collectd/files/collectd_systemd.service
+{%- else %}
+  - name: /etc/init/{{ remote_client.service }}
+  - source: salt://collectd/files/collectd_upstart.service
+{%- endif %}
+  - user: root
+  - mode: 644
+  - defaults:
+    service_name: {{ remote_client.service }}
+    config_file: {{ remote_client.config_file }}
+    automatic_starting: {{ remote_client.automatic_starting }}
+  - template: jinja
+  - require_in:
+    - service: {{ remote_client.service }}_service
+
+{%- endif %}
diff --git a/metadata/service/client/init.yml b/metadata/service/client/init.yml
index cbf9f47..6ef1257 100644
--- a/metadata/service/client/init.yml
+++ b/metadata/service/client/init.yml
@@ -7,3 +7,4 @@
     client:
       enabled: true
       read_interval: 60
+      use_fqdn: true
diff --git a/metadata/service/remote_client/cluster.yml b/metadata/service/remote_client/cluster.yml
new file mode 100644
index 0000000..b340e79
--- /dev/null
+++ b/metadata/service/remote_client/cluster.yml
@@ -0,0 +1,11 @@
+applications:
+- collectd
+classes:
+- service.collectd.support
+parameters:
+  collectd:
+    remote_client:
+      enabled: true
+      read_interval: 10
+      use_fqdn: false
+      automatic_starting: false
diff --git a/metadata/service/remote_client/single.yml b/metadata/service/remote_client/single.yml
new file mode 100644
index 0000000..81062b4
--- /dev/null
+++ b/metadata/service/remote_client/single.yml
@@ -0,0 +1,11 @@
+applications:
+- collectd
+classes:
+- service.collectd.support
+parameters:
+  collectd:
+    remote_client:
+      enabled: true
+      read_interval: 10
+      use_fqdn: false
+      automatic_starting: true