Merge "Fix order of hosts in the Grafana dashboard"
diff --git a/.kitchen.travis.yml b/.kitchen.travis.yml
new file mode 100644
index 0000000..6bcad13
--- /dev/null
+++ b/.kitchen.travis.yml
@@ -0,0 +1,6 @@
+suites:
+
+  - name: <%= ENV['SUITE'] %>
+    provisioner:
+      pillars-from-files:
+        influxdb.sls: tests/pillar/<%= ENV['SUITE'] %>.sls
diff --git a/.travis.yml b/.travis.yml
index 7a77247..2e36211 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,15 +17,19 @@
   - bundle install
 
 env:
-    - PLATFORM=trevorj/salty-whales:trusty
-    - PLATFORM=trevorj/salty-whales:xenial
+  - PLATFORM=trevorj/salty-whales:trusty SUITE=client
+  - PLATFORM=trevorj/salty-whales:xenial SUITE=client
+  - PLATFORM=trevorj/salty-whales:trusty SUITE=cluster
+  - PLATFORM=trevorj/salty-whales:xenial SUITE=cluster
+  - PLATFORM=trevorj/salty-whales:trusty SUITE=single
+  - PLATFORM=trevorj/salty-whales:xenial SUITE=single
 
 before_script:
   - set -o pipefail
   - make test | tail
 
 script:
-  - test ! -e .kitchen.yml || bundle exec kitchen test -t tests/integration
+  - KITCHEN_LOCAL_YAML=.kitchen.travis.yml bundle exec kitchen test -t tests/integration
 
 notifications:
   webhooks:
diff --git a/README.rst b/README.rst
index c79788f..ab729ac 100644
--- a/README.rst
+++ b/README.rst
@@ -264,6 +264,29 @@
             database: mydb1
             privilege: all
 
+InfluxDB relay with HTTP outputs:
+
+.. code-block:: yaml
+
+    influxdb:
+      relay:
+        enabled: true
+        listen:
+          http_backend:
+            type: http
+            bind:
+              address: 127.0.0.1
+              port: 9096
+            output:
+              server1:
+                location: http://server1:8086/write
+                timeout: 20s
+                buffer_size_mb: 512
+                max_batch_kb: 1024
+                max_delay_interval: 30s
+              server2:
+                location: http://server2:8086/write
+  
 Read more
 =========
 
diff --git a/influxdb/files/influxdb-relay.conf b/influxdb/files/influxdb-relay.conf
new file mode 100644
index 0000000..6d173a8
--- /dev/null
+++ b/influxdb/files/influxdb-relay.conf
@@ -0,0 +1,54 @@
+{%- from "influxdb/map.jinja" import relay with context -%}
+
+{%- if relay.get('enabled') %}
+
+{%- for name, listen in relay.listen.iteritems()|sort %}
+
+{%- if listen.get('enabled', True) and listen.get('type', 'http') in ('http', 'udp') %}
+
+{%- set listen_type = listen.get('type', 'http') %}
+[[{{ listen_type }}]]
+name = "{{ name }}"
+bind-addr = "{{ listen.bind.get('address', '0.0.0.0') }}:{{ listen.bind.port }}"
+{%- if listen_type == 'http' and listen.default_retention_policy is defined %}
+default-retention-policy = "{{ listen.default_retention_policy }}"
+{%- endif %}
+{%- if listen_type == 'udp' and listen.precision is defined %}
+precision = "{{ listen.precision }}"
+{%- endif %}
+{%- if listen_type == 'udp' and listen.read_buffer is defined %}
+read-buffer = {{ listen.read_buffer|int }}
+{%- endif %}
+
+{%- set outputs = [] %}
+{%- for output_name, output in listen.get('output', {}).iteritems()|sort %}
+  {%- set tmp = ['name = "{}"'.format(output_name), 'location = "{}"'.format( output.location)] %}
+  {%- if listen_type == 'http' and output.timeout is defined %}
+    {%- do tmp.append('timeout = "{}"'.format(output.timeout)) %}
+  {%- endif %}
+  {%- if listen_type == 'http' and output.buffer_size_mb is defined %}
+    {%- do tmp.append('buffer-size-mb = {}'.format(output.buffer_size_mb)) %}
+  {%- endif %}
+  {%- if listen_type == 'http' and output.max_batch_kb is defined %}
+    {%- do tmp.append('max-batch-kb = {}'.format(output.max_batch_kb)) %}
+  {%- endif %}
+  {%- if listen_type == 'http' and output.max_delay_interval is defined %}
+    {%- do tmp.append('max-delay-interval = "{}"'.format(output.max_delay_interval)) %}
+  {%- endif %}
+  {%- if listen_type == 'udp' and output.mtu is defined %}
+    {%- do tmp.append('mtu = {}'.format(output.mtu)) %}
+  {%- endif %}
+  {%- do outputs.append(tmp) %}
+{%- endfor %}
+
+output = [
+{%- for output in outputs %}
+  { {{ output|join(', ') }} },
+{%- endfor %}
+]
+
+{%- endif %}
+
+{%- endfor %}
+
+{%- endif %}
diff --git a/influxdb/init.sls b/influxdb/init.sls
index a878715..95c7731 100644
--- a/influxdb/init.sls
+++ b/influxdb/init.sls
@@ -6,4 +6,7 @@
 {%- if pillar.influxdb.client is defined %}
 - influxdb.client
 {%- endif %}
+{%- if pillar.influxdb.relay is defined %}
+- influxdb.relay
+{%- endif %}
 {%- endif %}
diff --git a/influxdb/map.jinja b/influxdb/map.jinja
index d2d4a89..88ba241 100644
--- a/influxdb/map.jinja
+++ b/influxdb/map.jinja
@@ -48,3 +48,11 @@
     'default': {
     },
 }, merge=salt['pillar.get']('influxdb:client')) %}
+
+{%- set relay = salt['grains.filter_by']({
+    'default': {
+        'pkgs': ['influxdb-relay'],
+        'service': 'influxdb-relay',
+        'listen': {},
+    },
+}, merge=salt['pillar.get']('influxdb:relay')) %}
diff --git a/influxdb/relay.sls b/influxdb/relay.sls
new file mode 100644
index 0000000..58f0413
--- /dev/null
+++ b/influxdb/relay.sls
@@ -0,0 +1,26 @@
+{%- from "influxdb/map.jinja" import relay with context %}
+{%- if relay.get('enabled') %}
+
+influxdb_relay_packages:
+  pkg.installed:
+  - names: {{ relay.pkgs }}
+
+influxdb_relay_config:
+  file.managed:
+  - name: //etc/influxdb-relay/influxdb-relay.conf
+  - source: salt://influxdb/files/influxdb-relay.conf
+  - template: jinja
+  - require:
+    - pkg: influxdb_relay_packages
+
+influxdb_relay_service:
+  service.running:
+  - enable: true
+  - name: {{ relay.service }}
+{%- if grains.get('noservices') %}
+  - onlyif: /bin/false
+{%- endif %}
+  - watch:
+    - file: influxdb_relay_config
+
+{%- endif %}
diff --git a/metadata/service/relay/cluster.yml b/metadata/service/relay/cluster.yml
new file mode 100644
index 0000000..4708ea3
--- /dev/null
+++ b/metadata/service/relay/cluster.yml
@@ -0,0 +1,38 @@
+applications:
+- influxdb
+classes:
+- service.influxdb.support
+parameters:
+  _param:
+    influxdb_relay_timeout: 10s
+    influxdb_relay_buffer_size_mb: 512
+    influxdb_relay_max_batch_kb: 512
+    influxdb_relay_max_delay_inteval: 10s
+  influxdb:
+    relay:
+      enabled: true
+      listen:
+        http:
+          type: http
+          bind:
+            address: ${_param:cluster_local_address}
+            port: 9096
+          output:
+            influxdb01:
+              location: http://${_param:cluster_node01_address}:8086/write
+              timeout: ${_param:influxdb_relay_timeout}
+              buffer_size_mb: ${_param:influxdb_relay_buffer_size_mb}
+              max_batch_kb: ${_param:influxdb_relay_max_batch_kb}
+              max_delay_interval: ${_param:influxdb_relay_max_delay_inteval}
+            influxdb02:
+              location: http://${_param:cluster_node02_address}:8086/write
+              timeout: ${_param:influxdb_relay_timeout}
+              buffer_size_mb: ${_param:influxdb_relay_buffer_size_mb}
+              max_batch_kb: ${_param:influxdb_relay_max_batch_kb}
+              max_delay_interval: ${_param:influxdb_relay_max_delay_inteval}
+            influxdb03:
+              location: http://${_param:cluster_node03_address}:8086/write
+              timeout: ${_param:influxdb_relay_timeout}
+              buffer_size_mb: ${_param:influxdb_relay_buffer_size_mb}
+              max_batch_kb: ${_param:influxdb_relay_max_batch_kb}
+              max_delay_interval: ${_param:influxdb_relay_max_delay_inteval}
diff --git a/tests/pillar/relay.sls b/tests/pillar/relay.sls
new file mode 100644
index 0000000..7da8b73
--- /dev/null
+++ b/tests/pillar/relay.sls
@@ -0,0 +1,29 @@
+influxdb:
+  relay:
+    enabled: true
+    listen:
+      http_backend:
+        type: http
+        bind:
+          address: 127.0.0.1
+          port: 9096
+        output:
+          server1:
+            location: http://server1:8086/write
+            timeout: 20s
+            buffer_size_mb: 512
+            max_batch_kb: 1024
+            max_delay_interval: 30s
+          server2:
+            location: http://server2:8086/write
+      udp_backend:
+        type: udp
+        bind:
+          address: 127.0.0.1
+          port: 9196
+        output:
+          server1:
+            location: http://server1:8086/write
+            mtu: 1500
+          server2:
+            location: http://server2:8086/write
diff --git a/tests/run_tests.sh b/tests/run_tests.sh
index a4cac88..29fb975 100755
--- a/tests/run_tests.sh
+++ b/tests/run_tests.sh
@@ -6,11 +6,13 @@
 CURDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
 METADATA=${CURDIR}/../metadata.yml
 FORMULA_NAME=$(cat $METADATA | python -c "import sys,yaml; print yaml.load(sys.stdin)['name']")
+FORMULA_META_DIR=${CURDIR}/../${FORMULA_NAME}/meta
 
 ## Overrideable parameters
 PILLARDIR=${PILLARDIR:-${CURDIR}/pillar}
 BUILDDIR=${BUILDDIR:-${CURDIR}/build}
 VENV_DIR=${VENV_DIR:-${BUILDDIR}/virtualenv}
+MOCK_BIN_DIR=${MOCK_BIN_DIR:-${CURDIR}/mock_bin}
 DEPSDIR=${BUILDDIR}/deps
 
 SALT_FILE_DIR=${SALT_FILE_DIR:-${BUILDDIR}/file_root}
@@ -40,6 +42,15 @@
     pip install salt${PIP_SALT_VERSION}
 }
 
+setup_mock_bin() {
+    # If some state requires a binary, a lightweight replacement for
+    # such binary can be put into MOCK_BIN_DIR for test purposes
+    if [ -d "${MOCK_BIN_DIR}" ]; then
+        PATH="${MOCK_BIN_DIR}:$PATH"
+        export PATH
+    fi
+}
+
 setup_pillar() {
     [ ! -d ${SALT_PILLAR_DIR} ] && mkdir -p ${SALT_PILLAR_DIR}
     echo "base:" > ${SALT_PILLAR_DIR}/top.sls
@@ -121,6 +132,7 @@
     [ -d ${BUILDDIR} ] && mkdir -p ${BUILDDIR}
 
     which salt-call || setup_virtualenv
+    setup_mock_bin
     setup_pillar
     setup_salt
     install_dependencies
@@ -130,7 +142,26 @@
     for pillar in ${PILLARDIR}/*.sls; do
         grep ${FORMULA_NAME}: ${pillar} &>/dev/null || continue
         state_name=$(basename ${pillar%.sls})
+        salt_run grains.set 'noservices' False force=True
+
+        echo "Checking state ${FORMULA_NAME}.${state_name} ..."
         salt_run --id=${state_name} state.show_sls ${FORMULA_NAME} || (log_err "Execution of ${FORMULA_NAME}.${state_name} failed"; exit 1)
+
+        # Check that all files in 'meta' folder can be rendered using any valid pillar
+        for meta in `find ${FORMULA_META_DIR} -type f`; do
+            meta_name=$(basename ${meta})
+            echo "Checking meta ${meta_name} ..."
+            salt_run --out=quiet --id=${state_name} cp.get_template ${meta} ${SALT_CACHE_DIR}/${meta_name} \
+              || (log_err "Failed to render meta ${meta} using pillar ${FORMULA_NAME}.${state_name}"; exit 1)
+            cat ${SALT_CACHE_DIR}/${meta_name}
+        done
+    done
+}
+
+real_run() {
+    for pillar in ${PILLARDIR}/*.sls; do
+        state_name=$(basename ${pillar%.sls})
+        salt_run --id=${state_name} state.sls ${FORMULA_NAME} || (log_err "Execution of ${FORMULA_NAME}.${state_name} failed"; exit 1)
     done
 }
 
@@ -159,6 +190,9 @@
     run)
         run
         ;;
+    real-run)
+        real_run
+        ;;
     *)
         prepare
         run