Merge "Remove old kernel via dpkg instead of apt"
diff --git a/.kitchen.yml b/.kitchen.yml
index f3fc5b6..e708ea2 100644
--- a/.kitchen.yml
+++ b/.kitchen.yml
@@ -29,9 +29,9 @@
   sudo: true
 
 platforms:
-  - name: <%=ENV['PLATFORM'] || 'ubuntu-xenial'%>
+  - name: <%=ENV['PLATFORM'] || 'ubuntu-xenial-2017.7'%>
     driver_config:
-      image: <%=ENV['PLATFORM'] || 'trevorj/salty-whales:xenial'%>
+      image: <%=ENV['PLATFORM'] || 'trevorj/salty-whales:xenial-2017.7'%>
       platform: ubuntu
 
 
diff --git a/.travis.yml b/.travis.yml
index 714c249..0b24242 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -22,12 +22,19 @@
   - bundle install
 
 env:
+  - PLATFORM=trevorj/salty-whales:trusty-2017.7 SUITE=network
+  - PLATFORM=trevorj/salty-whales:xenial-2017.7 SUITE=network
+  # - PLATFORM=trevorj/salty-whales:trusty-2017.7 SUITE=storage
+  # - PLATFORM=trevorj/salty-whales:xenial-2017.7 SUITE=storage
+  - PLATFORM=trevorj/salty-whales:trusty-2017.7 SUITE=system
+  - PLATFORM=trevorj/salty-whales:xenial-2017.7 SUITE=system
   - 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
+## Test on both Salt version until there is new test policy accepted
 
 before_script:
   - set -o pipefail
diff --git a/README.rst b/README.rst
index 1449ee1..027f493 100644
--- a/README.rst
+++ b/README.rst
@@ -341,6 +341,41 @@
             mode: 700
             makedirs: true
 
+Ensure presence of file by specifying it's source:
+
+.. code-block:: yaml
+
+    linux:
+      system:
+        file:
+          /tmp/test.txt:
+            source: http://example.com/test.txt
+            user: root
+            group: root
+            file_mode: 700
+            dir_mode: 700
+            encoding: utf-8
+            hash: <<md5 hash>>
+            makedirs: true
+
+Ensure presence of file by specifying it's contents:
+
+.. code-block:: yaml
+
+    linux:
+      system:
+        file:
+          /tmp/test.txt:
+            contents: |
+              line1
+              line2
+            user: root
+            group: root
+            file_mode: 700
+            dir_mode: 700
+            encoding: utf-8
+            hash: <<md5 hash>>
+            makedirs: true
 Kernel
 ~~~~~~
 
@@ -421,6 +456,51 @@
           governor: performance
 
 
+CGROUPS
+~~~~~~~
+
+Setup linux cgroups:
+
+.. code-block:: yaml
+
+    linux:
+      system:
+        cgroup:
+          enabled: true
+          group:
+            ceph_group_1:
+              controller:
+                cpu:
+                  shares:
+                    value: 250
+                cpuacct:
+                  usage:
+                    value: 0
+                cpuset:
+                  cpus:
+                    value: 1,2,3
+                memory:
+                  limit_in_bytes:
+                    value: 2G
+                  memsw.limit_in_bytes:
+                    value: 3G
+              mapping:
+                subjects:
+                - '@ceph'
+            generic_group_1:
+              controller:
+                cpu:
+                  shares:
+                    value: 250
+                cpuacct:
+                  usage:
+                    value: 0
+              mapping:
+                subjects:
+                - '*:firefox'
+                - 'student:cp'
+
+
 Shared Libraries
 ~~~~~~~~~~~~~~~~
 
@@ -1010,6 +1090,27 @@
             mtu: 9100
             ipflush_onchange: true
 
+Debian static proto interfaces
+
+When you are changing interface proto from dhcp in up state to static, you
+may need to flush ip addresses and restart interface to assign ip address from a managed file.
+For example, if you want to use the interface and the ip on the bridge.
+This can be done by setting the ``ipflush_onchange`` with combination
+``restart_on_ipflush`` param set to to true.
+
+.. code-block:: yaml
+
+    linux:
+      network:
+        interface:
+          eth1:
+            enabled: true
+            type: eth
+            proto: static
+            address: 10.1.0.22
+            netmask: 255.255.255.0
+            ipflush_onchange: true
+            restart_on_ipflush: true
 
 Concatinating and removing interface files
 
@@ -1367,6 +1468,7 @@
             type: dpdk_ovs_bridge
             address: 192.168.50.0
             netmask: 255.255.255.0
+            tag: 101
             mtu: 9000
 
 Linux storage
@@ -1542,6 +1644,29 @@
             - fujitsu_eternus_dxl
             - hitachi_vsp1000
 
+PAM LDAP integration
+
+.. code-block:: yaml
+
+    parameters:
+      linux:
+        system:
+          auth:
+            enabled: true
+            ldap:
+              enabled: true
+              binddn: cn=bind,ou=service_users,dc=example,dc=com
+              bindpw: secret
+              uri: ldap://127.0.0.1
+              base: ou=users,dc=example,dc=com
+              ldap_version: 3
+              pagesize: 65536
+              referrals: off
+              filter:
+                passwd: (&(&(objectClass=person)(uidNumber=*))(unixHomeDirectory=*))
+                shadow: (&(&(objectClass=person)(uidNumber=*))(unixHomeDirectory=*))
+                group:  (&(objectClass=group)(gidNumber=*))
+
 Disabled multipath (the default setup)
 
 .. code-block:: yaml
diff --git a/linux/files/cgconfig.conf b/linux/files/cgconfig.conf
new file mode 100644
index 0000000..0ea2e75
--- /dev/null
+++ b/linux/files/cgconfig.conf
@@ -0,0 +1,16 @@
+{%- from "linux/map.jinja" import system with context -%}
+##
+## This is cgconfig configuration file is managed by Salt
+##
+{%- for cgroup_name, cg in system.cgroup.group.iteritems() %}
+group {{ cgroup_name }} {
+  {%- for controller_name, controller in cg.controller.iteritems() %}
+        {{ controller_name }} {
+    {%- for v_name, v in controller.iteritems() %}
+                {{ controller_name }}.{{ v_name }}="{{ v.value }}";
+    {%- endfor %}
+
+        }
+  {%- endfor %}
+}
+{%- endfor %}
diff --git a/linux/files/cgrules.conf b/linux/files/cgrules.conf
new file mode 100644
index 0000000..116a9d6
--- /dev/null
+++ b/linux/files/cgrules.conf
@@ -0,0 +1,10 @@
+{%- from "linux/map.jinja" import system with context -%}
+##
+## This is cgrules configuration file is managed by Salt
+##
+#<user/group>         <controller(s)>         <cgroup>
+{%- for cgroup_name, cg in system.cgroup.group.iteritems() %}
+{%- for subject in cg.mapping.subjects %}
+{{ subject }}{% raw %}         {% endraw %}{%- for controller_name, controller in cg.controller.iteritems() -%}{{ controller_name }}{%- if not loop.last -%},{%- endif -%}{%- endfor -%}{% raw %}         {% endraw %}{{ cgroup_name }}
+{%- endfor %}
+{%- endfor %}
diff --git a/linux/files/mkhomedir b/linux/files/mkhomedir
new file mode 100644
index 0000000..43c6a49
--- /dev/null
+++ b/linux/files/mkhomedir
@@ -0,0 +1,6 @@
+Name: Create home directory during login
+Default: yes
+Priority: 0
+Session-Type: Additional
+Session-Final:
+    required    pam_mkhomedir.so        skel=/etc/skel  umask=0022 silent
diff --git a/linux/files/multipath.conf b/linux/files/multipath.conf
index 382d775..b32f1b3 100644
--- a/linux/files/multipath.conf
+++ b/linux/files/multipath.conf
@@ -10,7 +10,7 @@
 
 blacklist {
         {%- for device in storage.multipath.get('blacklist_devices', []) %}
-        wwid                  {{ salt['cmd.run']('/lib/udev/scsi_id -g -u '+device) }}
+        wwid                  {{ salt['cmd.shell']('/lib/udev/scsi_id -g -u '+device) }}
         {%- endfor %}
         devnode "^(ram|raw|loop|fd|md|dm-|sr|scd|st|nbd)[0-9]*"
 }
diff --git a/linux/files/nslcd.conf b/linux/files/nslcd.conf
new file mode 100644
index 0000000..772be85
--- /dev/null
+++ b/linux/files/nslcd.conf
@@ -0,0 +1,68 @@
+{%- from "linux/map.jinja" import ldap with context -%}
+# /etc/nslcd.conf
+# nslcd configuration file. See nslcd.conf(5)
+# for details.
+
+# The user and group nslcd should run as.
+uid {{ ldap.uid }}
+gid {{ ldap.gid }}
+
+{%- if ldap.enabled %}
+
+{%- if ldap.uri is defined %}
+# The location at which the LDAP server(s) should be reachable.
+uri {{ ldap.uri }}
+{%- endif %}
+
+{%- if ldap.base is defined %}
+# The search base that will be used for all queries.
+base {{ ldap.base }}
+{%- endif %}
+
+# The LDAP protocol version to use.
+ldap_version {{ ldap.version }}
+
+{%- if ldap.binddn is defined %}
+# The DN to bind with for normal lookups.
+binddn {{ ldap.binddn }}
+{%- if ldap.bindpw is defined %}
+bindpw {{ ldap.bindpw }}
+{%- endif %}
+{%- endif %}
+
+{%- if ldap.rootpwmoddn is defined %}
+# The DN used for password modifications by root.
+rootpwmoddn {{ ldap.rootpwmoddn }}
+{%- endif %}
+
+# SSL options
+#ssl off
+#tls_reqcert never
+#tls_cacertfile /etc/ssl/certs/ca-certificates.crt
+
+# The search scope.
+scope {{ ldap.scope }}
+
+{%- if ldap.pagesize is defined %}
+pagesize {{ ldap.pagesize }}
+{%- endif %}
+{%- if ldap.referrals is defined %}
+referrals {{ ldap.referrals }}
+{%- endif %}
+
+{%- if ldap.filter is defined %}
+# Filters
+{%- for key, value in ldap.filter.iteritems() %}
+filter {{ key }} {{ value }}
+{%- endfor %}
+{%- endif %}
+{%- if ldap.map is defined %}
+# Mappings
+{%- for map_name,map in ldap.map.iteritems() %}
+{%- for key, value in map.iteritems() %}
+map {{ map_name }} {{ key }} {{ value }}
+{%- endfor %}
+{%- endfor %}
+{%- endif %}
+
+{%- endif %}
diff --git a/linux/files/nsswitch.conf b/linux/files/nsswitch.conf
new file mode 100644
index 0000000..74f6332
--- /dev/null
+++ b/linux/files/nsswitch.conf
@@ -0,0 +1,21 @@
+{%- from "linux/map.jinja" import ldap with context -%}
+# /etc/nsswitch.conf
+#
+# Example configuration of GNU Name Service Switch functionality.
+# If you have the `glibc-doc-reference' and `info' packages installed, try:
+# `info libc "Name Service Switch"' for information about this file.
+
+passwd:         compat{%- if ldap.enabled %} ldap{%- endif %}
+group:          compat{%- if ldap.enabled %} ldap{%- endif %}
+shadow:         compat{%- if ldap.enabled %} ldap{%- endif %}
+gshadow:        files
+
+hosts:          files dns
+networks:       files
+
+protocols:      db files
+services:       db files
+ethers:         db files
+rpc:            db files
+
+netgroup:       nis
diff --git a/linux/files/pam-add-profile b/linux/files/pam-add-profile
new file mode 100644
index 0000000..4f1a5fd
--- /dev/null
+++ b/linux/files/pam-add-profile
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+if [ "$(basename $EDITOR 2>/dev/null)" == "$(basename $0)" ]; then
+    PROFILES=$(debconf-get-selections | grep libpam-runtime/profiles | awk '{$1=$2=$3=""; print $0}')
+
+    for profile in ${PROFILE[@]}; do
+        if [[ $PROFILES =~ $profile ]]; then
+            continue
+        else
+            PROFILES="${PROFILES}, ${profile}"
+        fi
+    done
+
+    for profile in /usr/share/pam-configs/*; do
+        profile_name=$(grep Name: $profile | cut -d ' ' -f 2-)
+        PROFILES=$(echo $PROFILES | sed s,$(basename $profile),"${profile_name}",g)
+    done
+
+    cat > $1 <<EOF
+libpam-runtime/profiles="${PROFILES}"
+EOF
+else
+    [ -z $1 ] && { echo "Usage: $0 [PROFILE]"; exit 1; }
+    export PROFILE="$*"
+    EDITOR=/usr/local/bin/pam-add-profile DEBIAN_FRONTEND=editor pam-auth-update --force
+fi
diff --git a/linux/map.jinja b/linux/map.jinja
index 29dba83..185c131 100644
--- a/linux/map.jinja
+++ b/linux/map.jinja
@@ -85,6 +85,37 @@
     },
 }, grain='os_family', merge=salt['pillar.get']('linux:system')) %}
 
+{% set auth = salt['grains.filter_by']({
+    'Arch': {
+        'enabled': false,
+    },
+    'RedHat': {
+        'enabled': false,
+    },
+    'Debian': {
+        'enabled': false,
+    },
+}, grain='os_family', merge=salt['pillar.get']('linux:system:auth')) %}
+
+{% set ldap = salt['grains.filter_by']({
+    'RedHat': {
+        'enabled': false,
+        'pkgs': ['openldap-clients', 'nss-pam-ldapd', 'authconfig'],
+        'version': '3',
+        'scope': 'sub',
+        'uid': 'nslcd',
+        'gid': 'nslcd',
+    },
+    'Debian': {
+        'enabled': false,
+        'pkgs': ['libnss-ldapd', 'libpam-ldapd'],
+        'version': '3',
+        'scope': 'sub',
+        'uid': 'nslcd',
+        'gid': 'nslcd',
+    },
+}, grain='os_family', merge=salt['pillar.get']('linux:system:auth:ldap')) %}
+
 {#    'network_name', #}
 
 {% set interface_params = [
@@ -145,7 +176,7 @@
         'hostname_file': '/etc/hostname',
         'bridge_pkgs': ['bridge-utils'],
         'ovs_pkgs': ['openvswitch-switch', 'bridge-utils'],
-        'dpdk_pkgs': ['dpdk', 'dpdk-dev', 'dpdk-dkms', 'dpdk-igb-uio-dkms', 'dpdk-rte-kni-dkms'],
+        'dpdk_pkgs': ['dpdk', 'dpdk-dev', 'dpdk-igb-uio-dkms', 'dpdk-rte-kni-dkms'],
         'network_manager': False,
         'systemd': {},
         'interface': {},
diff --git a/linux/meta/fluentd.yml b/linux/meta/fluentd.yml
index 6b4041c..c65d3fb 100644
--- a/linux/meta/fluentd.yml
+++ b/linux/meta/fluentd.yml
@@ -5,6 +5,49 @@
       gem: ['fluent-plugin-systemd']
   config:
     label:
+      default_metric:
+        filter:
+          metric_failed_user:
+            tag: metric.failed_user
+            type: prometheus
+            metric:
+              - name: failed_logins_total
+                type: counter
+                desc: The total number of failed logins.
+            label:
+              - name: host
+                value: ${Hostname}
+          metric_out_of_memory:
+            tag: metric.out_of_memory
+            type: prometheus
+            metric:
+              - name: out_of_memory_total
+                type: counter
+                desc: The total number of OOM.
+            label:
+              - name: host
+                value: ${Hostname}
+          metric_hdd_errors_parse:
+            tag: metric.hdd_errors
+            type: parser
+            key_name: Payload
+            parser:
+              type: regexp
+              format: '/(?<device>[sv]d[a-z]+\d*)/'
+          metric_hdd_errors:
+            tag: metric.hdd_errors
+            require:
+              - metric_hdd_errors_parse
+            type: prometheus
+            metric:
+              - name: hdd_errors_total
+                type: counter
+                desc: The total number of hdd errors.
+            label:
+              - name: host
+                value: ${Hostname}
+              - name: device
+                value: ${device}
       systemd:
         input:
           systemd:
@@ -37,11 +80,33 @@
             tag: systemd.source
             type: rewrite_tag_filter
             rule:
-              - name: service
-                regexp: '^(.*)\.(.*)$'
+              - name: ident
+                regexp: '^(.*)$'
                 result: __TAG__.$1
           push_to_default:
             tag: 'systemd.source.*'
+            type: copy
+            store:
+              - type: relabel
+                label: default_output
+              - type: rewrite_tag_filter
+                rule:
+                  - name: Payload
+                    regexp: '^Invalid user'
+                    result: metric.failed_user
+                  - name: Payload
+                    regexp: '^Out of memory'
+                    result: metric.out_of_memory
+                  - name: Payload
+                    regexp: >-
+                      'error.+[sv]d[a-z]+\d*'
+                    result: metric.hdd_errors
+                  - name: Payload
+                    regexp: >-
+                      '[sv]d[a-z]+\d*.+error'
+                    result: metric.hdd_errors
+          push_to_metric:
+            tag: 'metric.**'
             type: relabel
-            label: default_output
+            label: default_metric
 {%- endif %}
diff --git a/linux/meta/prometheus.yml b/linux/meta/prometheus.yml
index e8a26e3..b2911d5 100644
--- a/linux/meta/prometheus.yml
+++ b/linux/meta/prometheus.yml
@@ -32,6 +32,16 @@
         summary: 'Free open files for {{ $labels.path }} too low on {{ $labels.host }}'
         description: 'Host {{ $labels.host }}) will run out of free open files in less than 8 hours.'
       {% endraw %}
+    SystemDiskErrors:
+      if: 'increase(hdd_errors_total[5m]) > 0'
+      {% raw %}
+      labels:
+        severity: critical
+        service: system
+      annotations:
+        summary: 'Disk {{ $labels.device }} is failing'
+        description: 'The disk ({{ $labels.device }}) is reporting errors on {{ $labels.host }}.'
+      {% endraw %}
     SystemDiskSpaceFull:
       if: 'disk_used_percent >= 99 and disk_inodes_total > 0'
       {% raw %}
diff --git a/linux/network/dpdk.sls b/linux/network/dpdk.sls
index 1ac9e25..05fe05f 100644
--- a/linux/network/dpdk.sls
+++ b/linux/network/dpdk.sls
@@ -110,7 +110,7 @@
 
 linux_network_dpdk_bridge_interface_{{ interface_name }}:
   cmd.run:
-    - name: "ovs-vsctl add-br {{ interface_name }} -- set bridge {{ interface_name }} datapath_type=netdev"
+    - name: "ovs-vsctl add-br {{ interface_name }} -- set bridge {{ interface_name }} datapath_type=netdev{% if interface.tag is defined %} -- set port {{ interface_name }} tag={{ interface.tag }}{% endif %}"
     - unless: "ovs-vsctl show | grep {{ interface_name }}"
 
     {# OVS dpdk needs ip address for vxlan termination on bridge br-prv #}
diff --git a/linux/network/interface.sls b/linux/network/interface.sls
index 921ceac..b9c1c41 100644
--- a/linux/network/interface.sls
+++ b/linux/network/interface.sls
@@ -225,10 +225,20 @@
 
 linux_interface_ipflush_onchange_{{ interface_name }}:
   cmd.run:
-  - name: "/sbin/ip address flush dev {{ interface_name }}; ifdown {{ interface_name }} ;ifup {{ interface_name }};"
+  - name: "/sbin/ip address flush dev {{ interface_name }}"
   - onchanges:
     - network: linux_interface_{{ interface_name }}
 
+{%- if interface.get('restart_on_ipflush', False) %}
+
+linux_interface_restart_on_ipflush_{{ interface_name }}:
+  cmd.run:
+  - name: "ifdown {{ interface_name }}; ifup {{ interface_name }};"
+  - onchanges:
+    - cmd: linux_interface_ipflush_onchange_{{ interface_name }}
+
+{%- endif %}
+
 {%- endif %}
 
 {%- if salt['grains.get']('saltversion') < '2017.7' %}
diff --git a/linux/network/resolv.sls b/linux/network/resolv.sls
index db1a463..caecee3 100644
--- a/linux/network/resolv.sls
+++ b/linux/network/resolv.sls
@@ -6,10 +6,13 @@
   - source: salt://linux/files/resolv.conf
   - mode: 644
   - template: jinja
+  - follow_symlinks: false
+  - require:
+    - service: resolvconf_service
 
-linux_resolvconf_disable:
-  cmd.run:
-  - name: resolvconf --disable-updates
-  - onlyif: resolvconf --updates-are-enabled
+resolvconf_service:
+  service.dead:
+    - name: resolvconf
+    - enable: false
 
 {%- endif %}
diff --git a/linux/system/auth.sls b/linux/system/auth.sls
new file mode 100644
index 0000000..70bf6cb
--- /dev/null
+++ b/linux/system/auth.sls
@@ -0,0 +1,100 @@
+{%- from "linux/map.jinja" import auth with context %}
+
+{%- if auth.enabled %}
+
+{%- if auth.get('ldap', {}).get('enabled', False) %}
+{%- from "linux/map.jinja" import ldap with context %}
+
+{%- if grains.os_family == 'Debian' %}
+
+linux_auth_debconf_libnss-ldapd:
+  debconf.set:
+    - name: libnss-ldapd
+    - data:
+        libnss-ldapd/nsswitch:
+          type: 'multiselect'
+          value: 'group, passwd, shadow'
+        libnss-ldapd/clean_nsswitch:
+          type: 'boolean'
+          value: 'false'
+    - require_in:
+      - pkg: linux_auth_ldap_packages
+
+linux_auth_debconf_libpam-ldapd:
+  debconf.set:
+    - name: libpam-ldapd
+    - data:
+        libpam-ldapd/enable_shadow:
+          type: 'boolean'
+          value: 'true'
+
+{#- Setup mkhomedir and ldap PAM profiles #}
+linux_auth_mkhomedir_config:
+  file.managed:
+    - name: /usr/share/pam-configs/mkhomedir
+    - source: salt://linux/files/mkhomedir
+    - require:
+      - pkg: linux_auth_ldap_packages
+
+linux_auth_pam_add_profile:
+  file.managed:
+    - name: /usr/local/bin/pam-add-profile
+    - source: salt://linux/files/pam-add-profile
+    - mode: 755
+
+linux_auth_pam_add_profiles:
+  cmd.run:
+    - name: /usr/local/bin/pam-add-profile ldap mkhomedir
+    - unless: "debconf-get-selections | grep libpam-runtime/profiles | grep mkhomedir | grep ldap"
+    - watch:
+      - file: linux_auth_mkhomedir_config
+    - require:
+      - file: linux_auth_pam_add_profile
+      - pkg: linux_auth_ldap_packages
+
+{%- elif grains.os_family == 'RedHat' %}
+
+linux_auth_config:
+  cmd.run:
+    - name: "authconfig --enableldap --enableldapauth --enablemkhomedir --update"
+    - require:
+      - pkg: linux_auth_ldap_packages
+
+{%- else %}
+
+linux_auth_nsswitch_config_file:
+  file.managed:
+- name: /etc/nsswitch.conf
+  - source: salt://linux/files/nsswitch.conf
+  - template: jinja
+  - mode: 644
+  - require:
+    - pkg: linux_auth_ldap_packages
+  - watch_in:
+    - service: linux_auth_nslcd_service
+
+{%- endif %}
+
+linux_auth_ldap_packages:
+  pkg.installed:
+  - pkgs: {{ ldap.pkgs }}
+
+linux_auth_nslcd_config_file:
+  file.managed:
+  - name: /etc/nslcd.conf
+  - source: salt://linux/files/nslcd.conf
+  - template: jinja
+  - mode: 600
+  - require:
+    - pkg: linux_auth_ldap_packages
+  - watch_in:
+    - service: linux_auth_nslcd_service
+
+linux_auth_nslcd_service:
+  service.running:
+  - enable: true
+  - name: nslcd
+
+{%- endif %}
+
+{%- endif %}
diff --git a/linux/system/cgroup.sls b/linux/system/cgroup.sls
new file mode 100644
index 0000000..caac31c
--- /dev/null
+++ b/linux/system/cgroup.sls
@@ -0,0 +1,119 @@
+{%- from "linux/map.jinja" import system with context %}
+
+{%- if system.cgroup.enabled|default(True) %}
+
+cgroup_package:
+  pkg.installed:
+  - pkgs:
+    - cgroup-bin
+
+include:
+  - linux.system.grub
+
+/etc/default/grub.d/80-cgroup.cfg:
+  file.managed:
+  - contents: 'GRUB_CMDLINE_LINUX_DEFAULT="quiet cgroup_enable=memory swapaccount=1"'
+  - require:
+    - file: grub_d_directory
+{%- if grains.get('virtual_subtype', None) not in ['Docker', 'LXC'] %}
+  - watch_in:
+    - cmd: grub_update
+{%- endif %}
+
+/etc/cgconfig.conf:
+  file.managed:
+  - user: root
+  - group: root
+  - mode: 0644
+  - template: jinja
+  - source: salt://linux/files/cgconfig.conf
+{%- if grains.get('virtual_subtype', None) not in ['Docker', 'LXC'] %}
+  - check_cmd: /usr/sbin/cgconfigparser -l
+{%- endif %}
+
+/etc/cgrules.conf:
+  file.managed:
+  - user: root
+  - group: root
+  - mode: 0644
+  - template: jinja
+  - source: salt://linux/files/cgrules.conf
+
+/etc/default/cgred:
+  file.managed:
+  - contents: |
+      OPTIONS=-v --logfile=/var/log/cgrulesengd.log
+
+/etc/systemd/system/cgred.service:
+  file.managed:
+  - contents: |
+      [Unit]
+      Description=CGroups Rules Engine Daemon
+      After=syslog.target
+
+      [Service]
+      Type=forking
+      EnvironmentFile=-/etc/default/cgred
+      ExecStart=/usr/sbin/cgrulesengd $OPTIONS
+
+      [Install]
+      WantedBy=multi-user.target
+
+cgred_service_running:
+  service.running:
+  - enable: true
+  - names: ['cgred']
+  - watch:
+    - file: /etc/cgconfig.conf
+    - file: /etc/cgrules.conf
+    - file: /etc/default/cgred
+    - file: /etc/systemd/system/cgred.service
+  {%- if grains.get('noservices') %}
+  - onlyif: /bin/false
+  {%- endif %}
+
+{%- else %}
+
+cgred_service_dead:
+  service.dead:
+  - enable: false
+  - names: ['cgred']
+  {%- if grains.get('noservices') %}
+  - onlyif: /bin/false
+  {%- endif %}
+
+include:
+  - linux.system.grub
+
+remove_/etc/default/grub.d/80-cgroup.cfg:
+  file.absent:
+  - name: /etc/default/grub.d/80-cgroup.cfg
+  - require:
+    - file: grub_d_directory
+{%- if grains.get('virtual_subtype', None) not in ['Docker', 'LXC'] %}
+  - watch_in:
+    - cmd: grub_update
+{%- endif %}
+
+remove_/etc/systemd/system/cgred.service:
+  file.absent:
+  - name: /etc/systemd/system/cgred.service
+
+remove_/etc/cgconfig.conf:
+  file.absent:
+  - name: /etc/cgconfig.conf
+
+remove_/etc/cgrules.conf:
+  file.absent:
+  - name: /etc/cgrules.conf
+
+remove_/etc/default/cgred:
+  file.absent:
+  - name: /etc/default/cgred
+
+purge_cgroup_package:
+  pkg.purged:
+  - pkgs:
+    - cgroup-tools
+
+{%- endif %}
diff --git a/linux/system/file.sls b/linux/system/file.sls
new file mode 100644
index 0000000..24ca394
--- /dev/null
+++ b/linux/system/file.sls
@@ -0,0 +1,34 @@
+{%- from "linux/map.jinja" import system with context %}
+{%- if system.enabled %}
+
+{%- for file_name, file in system.file.iteritems() %}
+
+{{ file_name }}:
+  file.managed:
+    {%- if file.source is defined %}
+    - source: {{ file.source }}
+    {%- endif %}
+    {%- if file.contents is defined %}
+    - contents: {{ file.contents }}
+    {%- endif %}
+    - makedirs: {{ file.get('makedirs', 'True') }}
+    - user: {{ file.get('user', 'root') }}
+    - group: {{ file.get('group', 'root') }}
+    {%- if file.file_mode is defined %}
+    - file_mode: {{ file.file_mode }}
+    {%- endif %}
+    {%- if file.dir_mode is defined %}
+    - dir_mode: {{ file.dir_mode }}
+    {%- endif %}
+    {%- if file.encoding is defined %}
+    - encoding: {{ file.encoding }}
+    {%- endif %}
+    {%- if file.hash is defined %}
+    - source_hash: {{ file.hash }}
+    {%- else %}
+    - skip_verify: True
+    {%- endif %}
+
+{%- endfor %}
+
+{%- endif %}
\ No newline at end of file
diff --git a/linux/system/init.sls b/linux/system/init.sls
index 25008a8..8594d48 100644
--- a/linux/system/init.sls
+++ b/linux/system/init.sls
@@ -33,6 +33,9 @@
 {%- if system.sysfs is defined %}
 - linux.system.sysfs
 {%- endif %}
+{%- if system.cgroup is defined %}
+- linux.system.cgroup
+{%- endif %}
 {%- if system.locale|length > 0 %}
 - linux.system.locale
 {%- endif %}
@@ -99,9 +102,15 @@
 {%- if system.directory is defined %}
 - linux.system.directory
 {%- endif %}
+{%- if system.file is defined %}
+- linux.system.file
+{%- endif %}
 {%- if system.ld is defined %}
 - linux.system.ld
 {%- endif %}
 {%- if system.apt is defined and grains.os_family == 'Debian' %}
 - linux.system.apt
 {%- endif %}
+{%- if system.auth is defined %}
+- linux.system.auth
+{%- endif %}
diff --git a/linux/system/repo.sls b/linux/system/repo.sls
index 1ea921c..5d4d059 100644
--- a/linux/system/repo.sls
+++ b/linux/system/repo.sls
@@ -108,7 +108,7 @@
   {%- if repo.ppa is defined %}
   - ppa: {{ repo.ppa }}
   {%- else %}
-  - human_name: {{ name }}
+  - humanname: {{ name }}
   - name: {{ repo.source }}
   {%- if repo.architectures is defined %}
   - architectures: {{ repo.architectures }}
@@ -143,6 +143,18 @@
 {%- else %}
 
 linux_repo_{{ name }}_absent:
+  pkgrepo.absent:
+    {%- if repo.ppa is defined %}
+    - ppa: {{ repo.ppa }}
+    {%- if repo.key_id is defined %}
+    - keyid_ppa: {{ repo.keyid_ppa }}
+    {%- endif %}
+    {%- else %}
+    - file: /etc/apt/sources.list.d/{{ name }}.list
+    {%- if repo.key_id is defined %}
+    - keyid: {{ repo.key_id }}
+    {%- endif %}
+    {%- endif %}
   file.absent:
     - name: /etc/apt/sources.list.d/{{ name }}.list
 
@@ -150,17 +162,19 @@
 
 {%- endif %}
 
+{#- os_family Debian #}
 {%- endif %}
 
 {%- if grains.os_family == "RedHat" %}
 
+{%- if repo.get('enabled', True) %}
+
 {%- if repo.get('proxy', {}).get('enabled', False) %}
 # PLACEHOLDER
 # TODO, implement per proxy configuration for Yum
 {%- endif %}
 
 {%- if not repo.get('default', False) %}
-
 linux_repo_{{ name }}:
   pkgrepo.managed:
   - name: {{ name }}
@@ -176,11 +190,18 @@
   {%- endif %}
   - require:
     - pkg: linux_repo_prereq_pkgs
-
 {%- endif %}
 
+{#- repo.enabled is false #}
+{%- else %}
+  pkgrepo.absent:
+    - name: {{ repo.source }}
 {%- endif %}
 
+{#- os_family Redhat #}
+{%- endif %}
+
+{#- repo.iteritems() loop #}
 {%- endfor %}
 
 {%- if default_repos|length > 0 and grains.os_family == 'Debian' %}
diff --git a/tests/pillar/system.sls b/tests/pillar/system.sls
index 06a1bdc..d542587 100644
--- a/tests/pillar/system.sls
+++ b/tests/pillar/system.sls
@@ -19,6 +19,16 @@
     kernel:
       isolcpu: 1,2,3,4
       elevator: deadline
+    cgroup:
+      group:
+        group_1:
+          controller:
+            cpu:
+              shares:
+                value: 250
+          mapping:
+            subjects:
+            - '@group1'
     sysfs:
       scheduler:
         block/sda/queue/scheduler: deadline
@@ -51,6 +61,7 @@
       testuser:
         enabled: true
         name: testuser
+        password: passw0rd
         sudo: true
         uid: 9999
         full_name: Test User
diff --git a/tests/pillar/system_extra.sls b/tests/pillar/system_extra.sls
index 801c628..a425f6a 100644
--- a/tests/pillar/system_extra.sls
+++ b/tests/pillar/system_extra.sls
@@ -1,6 +1,21 @@
 
 linux:
   system:
+    auth:
+      enabled: true
+      ldap:
+        enabled: true
+        binddn: cn=bind,ou=service_users,dc=example,dc=com
+        bindpw: secret
+        uri: ldap://127.0.0.1
+        base: ou=users,dc=example,dc=com
+        ldap_version: 3
+        pagesize: 65536
+        referrals: off
+        filter:
+          passwd: (&(&(objectClass=person)(uidNumber=*))(unixHomeDirectory=*))
+          shadow: (&(&(objectClass=person)(uidNumber=*))(unixHomeDirectory=*))
+          group:  (&(objectClass=group)(gidNumber=*))
     enabled: true
     cluster: default
     name: linux