Merge "Apply dpdk port parameters no matter if it's bonded or not"
diff --git a/.kitchen.travis.yml b/.kitchen.travis.yml
index f847543..a1d3e04 100644
--- a/.kitchen.travis.yml
+++ b/.kitchen.travis.yml
@@ -3,4 +3,4 @@
   - name: <%= ENV['SUITE'] %>
     provisioner:
       pillars-from-files:
-        neutron.sls: tests/pillar/<%= ENV['SUITE'] %>.sls
+        linux.sls: tests/pillar/<%= ENV['SUITE'] %>.sls
diff --git a/.kitchen.vagrant.yml b/.kitchen.vagrant.yml
new file mode 100644
index 0000000..fb9981e
--- /dev/null
+++ b/.kitchen.vagrant.yml
@@ -0,0 +1,36 @@
+---
+driver:
+  name: vagrant
+  vm_hostname: linux.ci.local
+  use_sudo: false
+  customize:
+    memory: 1024
+
+
+provisioner:
+  name: salt_solo
+  salt_install: bootstrap
+  salt_bootstrap_url: https://bootstrap.saltstack.com
+  salt_version: latest
+  require_chef: false
+  log_level: error
+  formula: linux
+  grains:
+    noservices: true
+  state_top:
+    base:
+      "*":
+        - linux
+  pillars:
+    top.sls:
+      base:
+        "*":
+          - linux
+
+platforms:
+- name: ubuntu-16.04
+- name: ubuntu-14.04
+- name: centos-7.3
+- name: centos-6.8
+
+# vim: ft=yaml sw=2 ts=2 sts=2 tw=125
diff --git a/README.rst b/README.rst
index 6f81c5d..f62a937 100644
--- a/README.rst
+++ b/README.rst
@@ -407,6 +407,32 @@
         cpu:
           governor: performance
 
+Certificates
+~~~~~~~~~~~~
+
+Add certificate authority into system trusted CA bundle
+
+.. code-block:: yaml
+
+    linux:
+      system:
+        ca_certificates:
+          mycert: |
+            -----BEGIN CERTIFICATE-----
+            MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
+            A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
+            cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
+            MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
+            BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
+            YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
+            ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
+            BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
+            I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
+            CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
+            lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
+            AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
+            -----END CERTIFICATE-----
+
 Sysfs
 ~~~~~
 
@@ -687,6 +713,18 @@
 
 Possible status is dead (disable service by default), running (enable service by default), enabled, disabled.
 
+Linux with atop service:
+
+.. code-block:: yaml
+
+    linux:
+      system:
+        atop:
+          enabled: true
+          interval: 20
+          logpath: "/var/log/atop"
+          outfile: "/var/log/atop/daily.log"
+
 RHEL / CentOS
 ^^^^^^^^^^^^^
 
@@ -984,6 +1022,36 @@
                reject:
                  - 192.33.137.211
 
+Linux network systemd settings:
+
+.. code-block:: yaml
+
+    linux:
+      network:
+        ...
+        systemd:
+          link:
+            10-iface-dmz:
+              Match:
+                MACAddress: c8:5b:67:fa:1a:af
+                OriginalName: eth0
+              Link:
+                Name: dmz0
+          netdev:
+            20-bridge-dmz:
+              match:
+                name: dmz0
+              network:
+                mescription: bridge
+                bridge: br-dmz0
+          network:
+          # works with lowercase, keys are by default capitalized
+            40-dhcp:
+              match:
+                name: '*'
+              network:
+                DHCP: yes
+
 
 Configure global environment variables
 
diff --git a/_modules/linux_hosts.py b/_modules/linux_hosts.py
new file mode 100644
index 0000000..78853bd
--- /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(set(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/atop.conf b/linux/files/atop.conf
new file mode 100644
index 0000000..4474da7
--- /dev/null
+++ b/linux/files/atop.conf
@@ -0,0 +1,5 @@
+{%- from "linux/map.jinja" import system with context -%}
+# This file /etc/default/atop is managed by Salt linux formula
+INTERVAL={{ system.atop.interval }}
+LOGPATH={{ system.atop.logpath }}
+OUTFILE={{ system.atop.outfile }}
diff --git a/linux/files/atop.systemd b/linux/files/atop.systemd
new file mode 100644
index 0000000..be57dbb
--- /dev/null
+++ b/linux/files/atop.systemd
@@ -0,0 +1,16 @@
+{%- from "linux/map.jinja" import system with context -%}
+[Unit]
+Description=atop - advanced interactive monitor
+After=syslog.target
+ConditionPathExists={{ config_file }}
+Documentation=man:atop(1)
+Documentation=https://atoptool.nl
+
+[Service]
+EnvironmentFile=-{{ config_file }}
+ExecStart=/usr/bin/atop -a -w ${LOGPATH}/daily.log ${INTERVAL}
+Restart=always
+RestartSec=10
+
+[Install]
+WantedBy=multi-user.target
diff --git a/linux/files/collectd_bond_status.conf b/linux/files/collectd_bond_status.conf
new file mode 100644
index 0000000..05f52bb
--- /dev/null
+++ b/linux/files/collectd_bond_status.conf
@@ -0,0 +1,7 @@
+Import "bond_status"
+
+<Module "bond_status">
+  {%- for interface in plugin.get('interfaces', []) %}
+  Bond "{{ interface }}"
+  {%- endfor %}
+</Module>
diff --git a/linux/files/hosts b/linux/files/hosts
index 64f4113..4971f6c 100644
--- a/linux/files/hosts
+++ b/linux/files/hosts
@@ -32,8 +32,18 @@
     ],
 } -%}
 {%- 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/systemd-network.conf b/linux/files/systemd-network.conf
new file mode 100644
index 0000000..6a21b4f
--- /dev/null
+++ b/linux/files/systemd-network.conf
@@ -0,0 +1,8 @@
+{%- from "linux/map.jinja" import system with context -%}
+{%- for section, options in settings.iteritems() %}
+
+[{{ section[0].upper() + section[1:] }}]
+{%- for option, value in options.iteritems() %}
+{{ option[0].upper() + option[1:] }}={{ value }}
+{%- endfor %}
+{%- endfor %}
diff --git a/linux/map.jinja b/linux/map.jinja
index 771da0f..786410e 100644
--- a/linux/map.jinja
+++ b/linux/map.jinja
@@ -18,6 +18,13 @@
          },
         'selinux': 'permissive',
         'ca_certs_dir': '/usr/local/share/ca-certificates',
+        'atop': {
+             'enabled': false,
+             'interval': '20',
+             'autostart': true,
+             'logpath': '/var/log/atop',
+             'outfile': '/var/log/atop/daily.log'
+         },
     },
     'Debian': {
         'pkgs': ['python-apt', 'apt-transport-https', 'libmnl0'],
@@ -38,6 +45,13 @@
          },
         'selinux': 'permissive',
         'ca_certs_dir': '/usr/local/share/ca-certificates',
+        'atop': {
+             'enabled': false,
+             'interval': '20',
+             'autostart': true,
+             'logpath': '/var/log/atop',
+             'outfile': '/var/log/atop/daily.log'
+         },
     },
     'RedHat': {
         'pkgs': ['policycoreutils', 'policycoreutils-python', 'telnet', 'wget'],
@@ -58,6 +72,13 @@
          },
         'selinux': 'permissive',
         'ca_certs_dir': '/usr/local/share/ca-certificates',
+        'atop': {
+             'enabled': false,
+             'interval': '20',
+             'autostart': true,
+             'logpath': '/var/log/atop',
+             'outfile': '/var/log/atop/daily.log'
+         },
     },
 }, grain='os_family', merge=salt['pillar.get']('linux:system')) %}
 
@@ -92,6 +113,7 @@
     'updelay',
     'hashing-algorithm',
     'hardware-dma-ring-rx',
+    'hwaddr',
 ] %}
 
 {% set network = salt['grains.filter_by']({
@@ -101,6 +123,7 @@
         'ovs_pkgs': ['openvswitch-switch'],
         'hostname_file': '/etc/hostname',
         'network_manager': False,
+        'systemd': {},
         'interface': {},
         'interface_params': interface_params,
         'bridge': 'none',
@@ -117,6 +140,7 @@
         'ovs_pkgs': ['openvswitch-switch', 'bridge-utils'],
         'dpdk_pkgs': ['dpdk', 'dpdk-dev', 'dpdk-dkms', 'dpdk-igb-uio-dkms', 'dpdk-rte-kni-dkms'],
         'network_manager': False,
+        'systemd': {},
         'interface': {},
         'interface_params': interface_params,
         'bridge': 'none',
@@ -132,6 +156,7 @@
         'ovs_pkgs': ['openvswitch-switch', 'bridge-utils'],
         'hostname_file': '/etc/sysconfig/network',
         'network_manager': False,
+        'systemd': {},
         'interface': {},
         'interface_params': interface_params,
         'bridge': 'none',
@@ -200,24 +225,32 @@
 {% 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,
+        },
+        'bond_status': {
+            'interfaces': False
         },
         'cpu_idle_percentage': {
               'warn': 10.0,
diff --git a/linux/meta/collectd.yml b/linux/meta/collectd.yml
index d38f1ae..0884365 100644
--- a/linux/meta/collectd.yml
+++ b/linux/meta/collectd.yml
@@ -1,11 +1,17 @@
+{%- from "linux/map.jinja" import monitoring with context %}
 local_plugin:
   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 %}
@@ -45,3 +51,12 @@
     plugin: swap
     template: linux/files/collectd_swap.conf
     report_bytes: True
+  {%- if monitoring.bond_status.interfaces is defined and monitoring.bond_status.interfaces is list %}
+  linux_bond_status:
+    plugin: python
+    template: linux/files/collectd_bond_status.conf
+    interfaces:
+      {%- for interface in monitoring.bond_status.interfaces %}
+      - {{ interface }}
+      {%- endfor %}
+  {%- endif %}
diff --git a/linux/meta/heka.yml b/linux/meta/heka.yml
index d45504d..312263e 100644
--- a/linux/meta/heka.yml
+++ b/linux/meta/heka.yml
@@ -1,3 +1,4 @@
+{%- from "linux/map.jinja" import monitoring with context %}
 metric_collector:
   trigger:
     linux_system_cpu_critical:
@@ -136,6 +137,18 @@
         window: 60
         periods: 0
         function: max
+    {%- if monitoring.bond_status.interfaces is defined and monitoring.bond_status.interfaces is list %}
+    linux_bond_status_critical:
+      description: Bond members are down.
+      rules:
+      - function: last
+        metric: bond_status_links_down
+        periods: 0
+        relational_operator: '>'
+        threshold: 0
+        window: 120
+      severity: critical
+    {%- endif %}
   alarm:
     linux_system_cpu:
       alerting: enabled
@@ -167,3 +180,9 @@
       alerting: enabled_with_notification
       triggers:
       - linux_system_hdd_errors_critical
+    {%- if monitoring.bond_status.interfaces is defined and monitoring.bond_status.interfaces is list %}
+    linux_bond_status:
+      alerting: enabled
+      triggers:
+        - linux_bond_status_critical
+    {%- endif %}
diff --git a/linux/meta/logrotate.yml b/linux/meta/logrotate.yml
new file mode 100644
index 0000000..cd980f7
--- /dev/null
+++ b/linux/meta/logrotate.yml
@@ -0,0 +1,20 @@
+{%- from "linux/map.jinja" import system with context -%}
+
+{%- if system.atop.enabled %}
+job:
+  atop:
+    - files:
+        - {{ system.atop.logpath }}/atop*
+        - {{ system.atop.logpath }}/{{ system.atop.outfile }}
+      options:
+        - olddir {{ system.atop.logpath }}/old
+        - compress
+        - delaycompress
+        - missingok
+        - notifempty
+        - rotate: 10
+        - daily
+        - minsize: 20M
+        - maxsize: 500M
+        - postrotate: "if ! service atop status > /dev/null; then service atop restart > /dev/null; fi"
+{%- endif %}
diff --git a/linux/meta/prometheus.yml b/linux/meta/prometheus.yml
index d2b3d05..88d2cb7 100644
--- a/linux/meta/prometheus.yml
+++ b/linux/meta/prometheus.yml
@@ -21,6 +21,16 @@
         summary: 'Free space for {{ $labels.path }} too low on {{ $labels.host }}'
         description: 'The disk partition ({{ $labels.path }}) will be full in less than 8 hours on {{ $labels.host }}.'
       {% endraw %}
+    SystemDiskSpaceFull:
+      if: 'disk_used_percent >= 99 and disk_inodes_total > 0'
+      {% raw %}
+      labels:
+        severity: critical
+        service: system
+      annotations:
+        summary: 'Disk partition {{ $labels.path }} full on {{ $labels.host }}'
+        description: 'The disk partition ({{ $labels.path }}) is used at {{ $value }}% on {{ $labels.host }}.'
+      {% endraw %}
     SystemDiskInodesTooLow:
       if: 'predict_linear(disk_inodes_free[1h], 8*3600) < 0'
       {% raw %}
@@ -31,6 +41,16 @@
         summary: 'Free inodes for {{ $labels.path }} too low on {{ $labels.host }}'
         description: 'The disk inodes ({{ $labels.path }}) will be full in less than 8 hours on {{ $labels.host }}.'
       {% endraw %}
+    SystemDiskInodesFull:
+      if: 'disk_inodes_used / disk_inodes_total >= 99'
+      {% raw %}
+      labels:
+        severity: critical
+        service: system
+      annotations:
+        summary: 'Inodes for {{ $labels.path }} full on {{ $labels.host }}'
+        description: 'The disk inodes ({{ $labels.path }}) are used at {{ $value }}% on {{ $labels.host }}.'
+      {% endraw %}
     SystemMemoryAvailableLow:
       {%- set mem_avail_warn_threshold = monitoring.free_memory_percentage.warn|float %}
       if: avg_over_time(mem_available_percent[5m]) < {{ mem_avail_warn_threshold }}
@@ -80,16 +100,6 @@
       annotations:
         summary: 'Too many transmitted packets dropped on {{ $labels.host }} for interface {{ $labels.interface }}'
         description: 'The rate of transmitted packets which are dropped is too high on node {{ $labels.host }} for interface {{ $labels.interface }} (current value={{ $value }}/sec, threshold={% endraw %}{{ net_tx_dropped_threshold }}/sec)'
-    SystemSwapUsed:
-      {%- set swap_used_threshold = monitoring.swap.warn.strip('%')|float %}
-      if: avg_over_time(swap_used_percent[1m]) > {{ swap_used_threshold }}
-      {% raw %}
-      labels:
-        severity: warning
-        service: system
-      annotations:
-        summary: 'Swap usage too high on {{ $labels.host }}'
-        description: 'The average percentage of used swap is too high on node {{ $labels.host }} (current value={{ $value }}%, threshold={% endraw %}{{ swap_used_threshold }}%)'
     SystemSwapIn:
       {%- set swap_in_threshold = monitoring.swap_in_rate.warn %}
       if: rate(swap_in[2m]) > {{ swap_in_threshold }}
diff --git a/linux/meta/salt.yml b/linux/meta/salt.yml
index 4cb2e8e..9ca9ee9 100644
--- a/linux/meta/salt.yml
+++ b/linux/meta/salt.yml
@@ -21,7 +21,8 @@
   {%- set dns_records = [] %}
   {%- for host_name, host in network.host.items() %}
   {%- if host.get('grain', False) %}
-  {%- do dns_records.append(host.pop('grain')) %}
+  {%- do host.pop('grain') %}
+  {%- do dns_records.append(host) %}
   {%- endif %}
   {%- endfor %}
   dns_records: 
diff --git a/linux/meta/telegraf.yml b/linux/meta/telegraf.yml
index 2ff4386..693638b 100644
--- a/linux/meta/telegraf.yml
+++ b/linux/meta/telegraf.yml
@@ -5,6 +5,7 @@
       totalcpu: true
     disk:
       ignore_fs:
+        - aufs
         - rootfs
         - sysfs
         - proc
diff --git a/linux/network/init.sls b/linux/network/init.sls
index 21069d3..56b05a5 100644
--- a/linux/network/init.sls
+++ b/linux/network/init.sls
@@ -13,6 +13,9 @@
 {%- if network.dhclient is defined %}
 - linux.network.dhclient
 {%- endif %}
+{%- if network.systemd|length > 0 %}
+- linux.network.systemd
+{%- endif %}
 {%- if network.interface|length > 0 %}
 - linux.network.interface
 {%- endif %}
diff --git a/linux/network/systemd.sls b/linux/network/systemd.sls
new file mode 100644
index 0000000..a8e1f24
--- /dev/null
+++ b/linux/network/systemd.sls
@@ -0,0 +1,43 @@
+{%- from "linux/map.jinja" import network with context %}
+{%- if network.enabled and grains.get('init', None) == 'systemd' %}
+
+{%- if network.systemd is mapping %}
+{%- for config_type, configs in network.systemd.iteritems() %}
+
+{%- if config_type == 'link' %}
+/etc/udev/rules.d/80-net-setup-link.rules:
+  file.managed:
+    - makedirs: True
+    - content: ""
+{%- endif %}
+
+{%- for config_name, config in configs.iteritems() %}
+linux_network_systemd_networkd_{{ config_type }}_config_{{ config_name }}:
+  file.managed:
+    - name: /etc/systemd/network/{{ config_name }}.{{ config_type }}
+    - source: salt://linux/files/systemd-network.conf
+    - template: jinja
+    - makedirs: True
+    - defaults:
+        settings: {{ config }}
+    - watch_in:
+      - module: linux_network_systemd_reload
+      - module: linux_network_systemd_networkd
+{%- endfor %}
+{%- endfor %}
+
+linux_network_systemd_reload:
+  module.wait:
+  - name: service.systemctl_reload
+
+linux_network_systemd_networkd:
+  service.running:
+  - name: systemd-networkd
+  - init_delay: 10
+  - enable: True
+  - reload: True
+  - watch:
+    - module: linux_network_systemd_reload
+
+{%- endif %}
+{%- endif %}
diff --git a/linux/system/atop.sls b/linux/system/atop.sls
new file mode 100644
index 0000000..e31db8a
--- /dev/null
+++ b/linux/system/atop.sls
@@ -0,0 +1,76 @@
+{%- from "linux/map.jinja" import system with context %}
+
+{%- if system.atop.enabled %}
+
+atop_packages:
+  pkg.installed:
+    - name: atop
+
+atop_defaults:
+  file.managed:
+    - name: /etc/default/atop
+    - source: salt://linux/files/atop.conf
+    - template: jinja
+    - user: root
+    - group: root
+    - mode: 644
+
+atop_logpath:
+  file.directory:
+  - name: {{ system.atop.logpath }}
+  - user: root
+  - group: root
+  - mode: 750
+  - makedirs: true
+
+{%- if grains.get('init', None) == 'systemd' %}
+atop_systemd_file:
+  file.managed:
+  - name: /etc/systemd/system/atop.service
+  - source: salt://linux/files/atop.service
+  - user: root
+  - mode: 644
+  - defaults:
+    service_name: atop
+    config_file: /etc/default/atop
+    autostart: {{ system.atop.autostart }}
+  - template: jinja
+  - require_in:
+    - service: atop_service
+{%- endif %}
+
+atop_service:
+  service.running:
+    - name: atop
+    - enable: {{ system.atop.autostart }}
+    - watch:
+      - file: atop_defaults
+    {%- if grains.get('noservices') %}
+    - onlyif: /bin/false
+    {%- endif %}
+
+{%- else %}
+
+atop_service_stop:
+  service.dead:
+    - name: atop
+    - enable: false
+    - require_in:
+      - pkg: atop_pkg_purge
+    {%- if grains.get('noservices') %}
+    - onlyif: /bin/false
+    {%- endif %}
+
+atop_defaults_purge:
+  file.absent:
+    - names:
+      - /etc/default/atop
+      - /etc/systemd/system/atop.service
+    - require:
+      - pkg: atop_pkg_purge
+
+atop_pkg_purge:
+  pkg.purged:
+    - name: atop
+
+{%- endif %}
diff --git a/linux/system/init.sls b/linux/system/init.sls
index 0ba87aa..2f379f4 100644
--- a/linux/system/init.sls
+++ b/linux/system/init.sls
@@ -63,6 +63,9 @@
 {%- if system.apparmor is defined %}
 - linux.system.apparmor
 {%- endif %}
+{%- if pillar.linux.system.atop is defined %}
+- linux.system.atop
+{%- endif %}
 {%- if system.console is defined %}
 - linux.system.console
 {%- endif %}
diff --git a/tests/pillar/network.sls b/tests/pillar/network.sls
index 009228f..840fd84 100644
--- a/tests/pillar/network.sls
+++ b/tests/pillar/network.sls
@@ -88,3 +88,30 @@
       #     initial_interval: 12
       #     reject:
       #       - 10.0.4.0/24
+    systemd:
+      link:
+        10-iface-dmz:
+          match:
+            type: eth
+            # MACAddress: c8:5b:7f:a5:1a:da
+            # OriginalName: eth0
+          link:
+            name: dmz0
+      netdev:
+        20-bridge:
+          NetDev:
+             Name: br0
+             Kind: bridge
+        20-bridge-dmz:
+        # test all lowercase
+          match:
+            name: dmz0
+          network:
+            description: bridge
+            bridge: br-dmz0
+      network:
+        40-dhcp:
+          Match:
+            Name: '*'
+          Network:
+            DHCP: yes
diff --git a/tests/pillar/system.sls b/tests/pillar/system.sls
index f39fdde..411323c 100644
--- a/tests/pillar/system.sls
+++ b/tests/pillar/system.sls
@@ -346,3 +346,8 @@
         192.168.0.1:
           mac: "ff:ff:ff:ff:ff:ff"
           interface: bond0
+    atop:
+      enabled: true
+      interval: 20
+      logpath: "/var/mylog/atop"
+      outfile: "/var/mylog/atop/daily.log"