Merge "Add bond member status monitoring."
diff --git a/.kitchen.travis.yml b/.kitchen.travis.yml
new file mode 100644
index 0000000..a1d3e04
--- /dev/null
+++ b/.kitchen.travis.yml
@@ -0,0 +1,6 @@
+suites:
+
+  - name: <%= ENV['SUITE'] %>
+    provisioner:
+      pillars-from-files:
+        linux.sls: tests/pillar/<%= ENV['SUITE'] %>.sls
diff --git a/.travis.yml b/.travis.yml
index 577330b..3e54539 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -22,17 +22,19 @@
   - bundle install
 
 env:
-    - PLATFORM=trevorj/salty-whales:trusty
-    - PLATFORM=trevorj/salty-whales:xenial
-
+  - PLATFORM=trevorj/salty-whales:trusty SUITE=network
+  - PLATFORM=trevorj/salty-whales:xenial SUITE=network
+  # - PLATFORM=trevorj/salty-whales:trusty SUITE=storage
+  # - PLATFORM=trevorj/salty-whales:xenial SUITE=storage
+  - PLATFORM=trevorj/salty-whales:trusty SUITE=system
+  - PLATFORM=trevorj/salty-whales:xenial SUITE=system
 
 before_script:
   - set -o pipefail
   - make test | tail
 
 script:
-  - test ! -e .kitchen.yml || bundle exec kitchen converge || true
-  - test ! -e .kitchen.yml || bundle exec kitchen verify -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 9e1692b..6f81c5d 100644
--- a/README.rst
+++ b/README.rst
@@ -407,6 +407,25 @@
         cpu:
           governor: performance
 
+Sysfs
+~~~~~
+
+Install sysfsutils and set sysfs attributes:
+
+.. code-block:: yaml
+
+    linux:
+      system:
+        sysfs:
+          scheduler:
+            block/sda/queue/scheduler: deadline
+          power:
+            mode:
+              power/state: 0660
+            owner:
+              power/state: "root:power"
+            devices/system/cpu/cpu0/cpufreq/scaling_governor: powersave
+
 Huge Pages
 ~~~~~~~~~~~~
 
@@ -967,10 +986,9 @@
 
 
 Configure global environment variables
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Linux /etc/environment:
-``/etc/environment`` is for static system wide variable assignment after boot. Variable expansion is frequently not supported.
+Use ``/etc/environment`` for static system wide variable assignment after
+boot. Variable expansion is frequently not supported.
 
 .. code-block:: yaml
 
@@ -1002,11 +1020,10 @@
             - .local
 
 Configure profile.d scripts
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Linux /etc/profile.d:
-The profile.d scripts are being sourced during .sh execution and support variable expansion in opposite to /etc/environment
-global settings in ``/etc/environment``.
+The profile.d scripts are being sourced during .sh execution and support
+variable expansion in opposite to /etc/environment global settings in
+``/etc/environment``.
 
 .. code-block:: yaml
 
@@ -1029,11 +1046,11 @@
             export NO_PROXY='.local'
 
 Linux with hosts
-~~~~~~~~~~~~~~~~
 
 Parameter purge_hosts will enforce whole /etc/hosts file, removing entries
 that are not defined in model except defaults for both IPv4 and IPv6 localhost
 and hostname + fqdn.
+
 It's good to use this option if you want to ensure /etc/hosts is always in a
 clean state however it's not enabled by default for safety.
 
@@ -1041,7 +1058,6 @@
 
     linux:
       network:
-        ...
         purge_hosts: true
         host:
           # No need to define this one if purge_hosts is true
@@ -1061,9 +1077,27 @@
             - node2.domain.com
             - service2.domain.com
 
+Linux with hosts collected from mine
+
+In this case all dns records defined within infrastrucuture will be passed to
+local hosts records or any DNS server. Only hosts with `grain` parameter to
+true will be propagated to the mine.
+
+.. code-block:: yaml
+
+    linux:
+      network:
+        purge_hosts: true
+        mine_dns_records: true
+        host:
+          node1:
+            address: 192.168.10.200
+            grain: true
+            names:
+            - node2.domain.com
+            - service2.domain.com
 
 Setup resolv.conf, nameservers, domain and search domains
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 .. code-block:: yaml
 
@@ -1082,7 +1116,7 @@
           - timeout: 2
           - attempts: 2
 
-**setting custom TX queue length for tap interfaces**
+setting custom TX queue length for tap interfaces
 
 .. code-block:: yaml
 
@@ -1091,7 +1125,6 @@
         tap_custom_txqueuelen: 10000
 
 DPDK OVS interfaces
---------------------
 
 **DPDK OVS NIC**
 
diff --git a/_modules/linux_hosts.py b/_modules/linux_hosts.py
new file mode 100644
index 0000000..08741ec
--- /dev/null
+++ b/_modules/linux_hosts.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+'''
+Module for defining new filter for sorting
+host names/alias by FQDN first and alphabetically
+'''
+
+from jinja2 import Undefined
+
+def fqdn_sort_fn(n1, n2):
+    l1 = n1.split('.')
+    l2 = n2.split('.')
+    if len(l1) > len(l2):
+        return -1
+    if len(l1) < len(l2):
+        return 1
+    for i1, i2 in zip(l1, l2):
+        if i1 < i2:
+            return -1
+        if i1  > i2:
+            return 1
+    return 0
+
+def fqdn_sort_filter(iterable):
+    if iterable is None or isinstance(iterable, Undefined):
+        return iterable
+    # Do effective custom sorting of iterable here
+    return sorted(iterable, cmp=fqdn_sort_fn)
diff --git a/_modules/linux_netlink.py b/_modules/linux_netlink.py
index 9e6df55..52d8a1d 100644
--- a/_modules/linux_netlink.py
+++ b/_modules/linux_netlink.py
@@ -2,16 +2,15 @@
 
 import re
 
-_alphanum_re = re.compile(r'^[a-z0-9]+$')
-_lo_re = re.compile(r'^lo$')
 
-
-def _filter(interface):
-    return _alphanum_re.match(interface) and not _lo_re.match(interface)
-
-
-def ls():
+def ls(regex):
     """
     Provide a list of network interfaces.
     """
+    _lo_re = re.compile(r'^lo$')
+    _alphanum_re = re.compile(regex)
+
+    def _filter(interface):
+        return _alphanum_re.match(interface) and not _lo_re.match(interface)
+
     return filter(_filter, __salt__['grains.get']('ip_interfaces', {}).keys())
diff --git a/linux/files/hosts b/linux/files/hosts
index 66951f0..4971f6c 100644
--- a/linux/files/hosts
+++ b/linux/files/hosts
@@ -31,9 +31,19 @@
         'ip6-allhosts'
     ],
 } -%}
-{%- for name, host in network.host.iteritems() -%}
+{%- for name, host in host_dict.iteritems() -%}
+{%- for hname in host.names -%}
+{%- if hname in hosts.get('127.0.1.1', []) -%}
+{%- do hosts.pop('127.0.1.1') -%}
+{%- endif %}
+{%- endfor %}
 {%- do hosts.update({host.address: host.names}) -%}
 {%- endfor %}
 {% for address, entries in hosts|dictsort %}
-{{ address }}   {{ entries|join(' ') }}
+{%- if 'linux_hosts.fqdn_sort_filter' in salt.keys() %}
+{%- set sorted_entries = salt['linux_hosts.fqdn_sort_filter'](entries) -%}
+{%- else %}
+{%- set sorted_entries = entries -%}
+{%- endif %}
+{{ address }}   {{ sorted_entries|join(' ') }}
 {%- endfor %}
diff --git a/linux/files/sysfs.conf b/linux/files/sysfs.conf
new file mode 100644
index 0000000..513a53a
--- /dev/null
+++ b/linux/files/sysfs.conf
@@ -0,0 +1,16 @@
+# Sysfs file for {{ name }} managed by salt-minion(1)
+#   DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
+
+{%- for key, value in sysfs.iteritems() %}
+  {%- if key in ["mode", "owner"] %}
+    {%- for attr, val in value.iteritems() %}
+mode {{ attr }} = {{ val }}
+    {%- endfor %}
+  {%- else %}
+{{ key }} = {{ value }}
+  {%- endif %}
+{%- endfor %}
+
+{#-
+vim: syntax=jinja
+-#}
diff --git a/linux/map.jinja b/linux/map.jinja
index 05af281..a27906c 100644
--- a/linux/map.jinja
+++ b/linux/map.jinja
@@ -108,6 +108,7 @@
            'host': 'none',
         },
         'host': {},
+        'mine_dns_records': False,
         'dhclient_config': '/etc/dhcp/dhclient.conf',
     },
     'Debian': {
@@ -123,6 +124,7 @@
            'host': 'none'
         },
         'host': {},
+        'mine_dns_records': False,
         'dhclient_config': '/etc/dhcp/dhclient.conf',
     },
     'RedHat': {
@@ -137,6 +139,7 @@
            'host': 'none'
         },
         'host': {},
+        'mine_dns_records': False,
         'dhclient_config': '/etc/dhcp/dhclient.conf',
     },
 }, grain='os_family', merge=salt['pillar.get']('linux:network')) %}
@@ -197,24 +200,29 @@
 {% set monitoring = salt['grains.filter_by']({
     'default': {
         'zombie': {
-              'warn': 3,
-              'crit': 7,
+            'warn': 3,
+            'crit': 7,
         },
         'procs': {
-              'warn': 5000,
-              'crit': 10000,
+            'warn': 5000,
+            'crit': 10000,
         },
         'load': {
-              'warn': '6,4,2',
-              'crit': '12,8,4',
+            'warn': '6,4,2',
+            'crit': '12,8,4',
         },
         'swap': {
-              'warn': '50%',
-              'crit': '20%',
+            'warn': '50%',
+            'crit': '20%',
         },
         'disk': {
-              'warn': '15%',
-              'crit': '5%',
+            'warn': '15%',
+            'crit': '5%',
+        },
+        'netlink': {
+            'interfaces': [],
+            'interface_regex': '^[a-z0-9]+$',
+            'ignore_selected': False,
         },
         'cpu_idle_percentage': {
               'warn': 10.0,
diff --git a/linux/meta/collectd.yml b/linux/meta/collectd.yml
index 7219cfd..0884365 100644
--- a/linux/meta/collectd.yml
+++ b/linux/meta/collectd.yml
@@ -3,10 +3,15 @@
   linux_network_netlink:
     plugin: netlink
     template: linux/files/collectd_netlink.conf
-    ignore_selected: false
-    {%- if 'linux_netlink.ls' in salt.keys() %}
+    ignore_selected: {{ monitoring.netlink.ignore_selected }}
+    {%- if monitoring.netlink.interfaces is list and monitoring.netlink.interfaces|length > 0 %}
+    {%- set interfaces = monitoring.netlink.interfaces %}
+    {%- else %}
+    {%- set interfaces = salt['linux_netlink.ls'](monitoring.netlink.interface_regex) %}
+    {%- endif %} 
+    {%- if interfaces %}
     interfaces:
-    {%- for interface_name in salt['linux_netlink.ls']() %}
+    {%- for interface_name in interfaces|sort %}
     - {{ interface_name }}
     {%- endfor %}
     {%- endif %}
diff --git a/linux/meta/salt.yml b/linux/meta/salt.yml
index 0a07522..9ca9ee9 100644
--- a/linux/meta/salt.yml
+++ b/linux/meta/salt.yml
@@ -1,4 +1,4 @@
-{%- from "linux/map.jinja" import system with context -%}
+{%- from "linux/map.jinja" import system,network with context -%}
 orchestrate:
   system:
     priority: 30
@@ -18,3 +18,12 @@
       {%- endif %}
     {%- endfor %}
     {{ service_grains|yaml(False)|indent(4) }}
+  {%- set dns_records = [] %}
+  {%- for host_name, host in network.host.items() %}
+  {%- if host.get('grain', False) %}
+  {%- do host.pop('grain') %}
+  {%- do dns_records.append(host) %}
+  {%- endif %}
+  {%- endfor %}
+  dns_records: 
+    dns_records: {{ dns_records|yaml }}
diff --git a/linux/network/host.sls b/linux/network/host.sls
index caa7f3b..14bd837 100644
--- a/linux/network/host.sls
+++ b/linux/network/host.sls
@@ -1,6 +1,21 @@
 {%- from "linux/map.jinja" import network with context %}
 {%- if network.enabled %}
 
+{%- set host_dict = network.host %}
+
+{%- if network.mine_dns_records %}
+
+{%- for node_name, node_grains in salt['mine.get']('*', 'grains.items').iteritems() %}
+{%- if node_grains.get('dns_records', []) is iterable %}
+{%- for record in node_grains.get('dns_records', []) %}
+{%- set record_key = node_name ~ '-' ~ loop.index %}
+{%- do host_dict.update({ record_key: {'address': record.address, 'names': record.names} }) %}
+{%- endfor %}
+{%- endif %}
+{%- endfor %}
+
+{%- endif %}
+
 {%- if network.get('purge_hosts', false) %}
 
 linux_hosts:
@@ -8,10 +23,12 @@
     - name: /etc/hosts
     - source: salt://linux/files/hosts
     - template: jinja
+    - defaults:
+        host_dict: {{ host_dict|yaml }}
 
 {%- else %}
 
-{%- for name, host in network.host.iteritems() %}
+{%- for name, host in host_dict.iteritems() %}
 
 {%- if host.names is defined %}
 
diff --git a/linux/storage/disk.sls b/linux/storage/disk.sls
index 55b8920..4fc96e5 100644
--- a/linux/storage/disk.sls
+++ b/linux/storage/disk.sls
@@ -36,9 +36,18 @@
     - module: create_disk_label_{{ disk_name }}
     - pkg: xfsprogs
 
-{%- if partition.get('mkfs') %}
+{% set end_size = end_size + partition.size -%}
 
-{%- if partition.type == "xfs" %}
+{%- endfor %}
+
+probe_partions_{{ disk_name }}:
+  module.run:
+  - name: partition.probe
+  - device: {{ disk_name }}
+
+{%- for partition in disk.get('partitions', []) %}
+
+{%- if partition.get('mkfs') and partition.type == "xfs" %}
 
 mkfs_partition_{{ disk_name }}_{{ loop.index }}:
   module.run:
@@ -50,11 +59,8 @@
 
 {%- endif %}
 
-{%- endif %}
-
-{% set end_size = end_size + partition.size -%}
-
 {%- endfor %}
+
 {%- endfor %}
 
 {%- endif %}
diff --git a/linux/system/cpu.sls b/linux/system/cpu.sls
index 4bf6564..37c3c21 100644
--- a/linux/system/cpu.sls
+++ b/linux/system/cpu.sls
@@ -1,16 +1,8 @@
 {%- from "linux/map.jinja" import system with context %}
 {%- if system.cpu.governor is defined %}
 
-linux_sysfs_package:
-  pkg.installed:
-    - pkgs:
-      - sysfsutils
-    - refresh: true
-
-/etc/sysfs.d:
-  file.directory:
-    - require:
-      - pkg: linux_sysfs_package
+include:
+  - linux.system.sysfs
 
 ondemand_service_disable:
   service.dead:
diff --git a/linux/system/init.sls b/linux/system/init.sls
index fe54147..0ba87aa 100644
--- a/linux/system/init.sls
+++ b/linux/system/init.sls
@@ -30,6 +30,9 @@
 {%- if system.cpu is defined %}
 - linux.system.cpu
 {%- endif %}
+{%- if system.sysfs is defined %}
+- linux.system.sysfs
+{%- endif %}
 {%- if system.locale|length > 0 %}
 - linux.system.locale
 {%- endif %}
diff --git a/linux/system/sysfs.sls b/linux/system/sysfs.sls
new file mode 100644
index 0000000..fdf1686
--- /dev/null
+++ b/linux/system/sysfs.sls
@@ -0,0 +1,42 @@
+{%- from "linux/map.jinja" import system with context %}
+
+linux_sysfs_package:
+  pkg.installed:
+    - pkgs:
+      - sysfsutils
+    - refresh: true
+
+/etc/sysfs.d:
+  file.directory:
+    - require:
+      - pkg: linux_sysfs_package
+
+{%- for name, sysfs in system.get('sysfs', {}).iteritems() %}
+
+/etc/sysfs.d/{{ name }}.conf:
+  file.managed:
+    - source: salt://linux/files/sysfs.conf
+    - template: jinja
+    - user: root
+    - group: root
+    - mode: 0644
+    - defaults:
+        name: {{ name }}
+        sysfs: {{ sysfs|yaml }}
+    - require:
+      - file: /etc/sysfs.d
+
+  {%- for key, value in sysfs.iteritems() %}
+    {%- if key not in ["mode", "owner"] %}
+      {%- if grains.get('virtual_subtype', None) not in ['Docker', 'LXC'] %}
+      {#- Sysfs cannot be set in docker, LXC, etc. #}
+linux_sysfs_write_{{ name }}_{{ key }}:
+  module.run:
+    - name: sysfs.write
+    - key: {{ key }}
+    - value: {{ value }}
+      {%- endif %}
+    {%- endif %}
+  {%- endfor %}
+
+{%- endfor %}
diff --git a/tests/pillar/system.sls b/tests/pillar/system.sls
index 8d39312..f39fdde 100644
--- a/tests/pillar/system.sls
+++ b/tests/pillar/system.sls
@@ -15,6 +15,15 @@
       default: "linux.ci.local$"
     kernel:
       isolcpu: 1,2,3,4
+    sysfs:
+      scheduler:
+        block/sda/queue/scheduler: deadline
+      power:
+        mode:
+          power/state: 0660
+        owner:
+          power/state: "root:power"
+        devices/system/cpu/cpu0/cpufreq/scaling_governor: powersave
     motd:
       - warning: |
           #!/bin/sh