Merge "fix typo sub_folders"
diff --git a/.kitchen.travis.yml b/.kitchen.travis.yml
new file mode 100644
index 0000000..bbf9c0d
--- /dev/null
+++ b/.kitchen.travis.yml
@@ -0,0 +1,6 @@
+suites:
+
+  - name: <%= ENV['SUITE'] %>
+    provisioner:
+      pillars-from-files:
+        reclass.sls: tests/pillar/<%= ENV['SUITE'] %>.sls
diff --git a/.travis.yml b/.travis.yml
index 4f34af2..79c2532 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,16 +17,23 @@
   - bundle install
 
 env:
-    - PLATFORM=trevorj/salty-whales:trusty
-    - PLATFORM=trevorj/salty-whales:xenial
-
+    - PLATFORM=trevorj/salty-whales:trusty SUITE=class_mapping
+    - PLATFORM=trevorj/salty-whales:xenial SUITE=class_mapping
+    - PLATFORM=trevorj/salty-whales:trusty SUITE=generate_multi
+    - PLATFORM=trevorj/salty-whales:xenial SUITE=generate_multi
+    - PLATFORM=trevorj/salty-whales:trusty SUITE=generate_single
+    - PLATFORM=trevorj/salty-whales:xenial SUITE=generate_single
+    - PLATFORM=trevorj/salty-whales:trusty SUITE=storage_local
+    - PLATFORM=trevorj/salty-whales:xenial SUITE=storage_local
+    - PLATFORM=trevorj/salty-whales:trusty SUITE=storage_nodes_uri
+    - PLATFORM=trevorj/salty-whales:xenial SUITE=storage_nodes_uri
 
 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 73cdb83..66883b8 100644
--- a/README.rst
+++ b/README.rst
@@ -41,6 +41,11 @@
 .. literalinclude:: tests/pillar/generate_multi.sls
    :language: yaml
 
+Reclass model with multiple node defined and interpolation enabled
+
+.. literalinclude:: tests/pillar/generate_multi_interpolate.sls
+   :language: yaml
+
 Reclass storage with simple class mappings
 
 .. literalinclude:: tests/pillar/class_mapping.sls
diff --git a/_states/reclass.py b/_states/reclass.py
index 4f9e09e..12e7ba9 100644
--- a/_states/reclass.py
+++ b/_states/reclass.py
@@ -72,6 +72,10 @@
     node = __salt__['reclass.node_get'](name, **kwargs)
 
     if 'Error' in node:
+        if __opts__['test']:
+            ret['result'] = None
+            ret['comment'] = 'Node "{0}" would be created'.format(name)
+            return ret
         # Create node
         __salt__['reclass.node_create'](name, path, cluster, environment, classes, parameters, **kwargs)
         ret['comment'] = 'Node "{0}" has been created'.format(name)
@@ -93,6 +97,11 @@
            'result': True,
            'comment': 'Node "{0}" already exists and it is in correct state'.format(name)}
 
+    if __opts__['test']:
+        ret['result'] = None
+        ret['comment'] = 'Classification of node "{0}" would be updated'.format(name)
+        return ret
+
     classify_ret = __salt__['reclass.node_classify'](name, node_data, class_mapping, **kwargs)
     ret['comment'] = 'Node "{0}" has been created'.format(name)
     ret['changes']['Node'] = classify_ret
@@ -100,6 +109,40 @@
     return ret
 
 
+def node_absent(name, **kwargs):
+    '''
+    Delete node from reclass metadata
+
+    :param name: node minion ID
+
+    '''
+    ret = {'name': name,
+           'changes': {},
+           'result': True,
+           'comment': 'Node "{0}" already absent'.format(name)}
+
+    # Check if node is present
+    node = __salt__['reclass.node_get'](name, **kwargs)
+    if 'Error' in node:
+        return ret
+
+    if __opts__['test']:
+        ret['result'] = None
+        ret['comment'] = 'Node "{0}" would be deleted'.format(name)
+        return ret
+
+    delete_ret = __salt__['reclass.node_delete'](name, **kwargs)
+    if 'Error' in delete_ret:
+        ret['result'] = False
+        ret['comment'] = delete_ret.get('Error', '')
+        return ret
+
+    ret['comment'] = 'Node "{0}" has been deleted'.format(name)
+    ret['changes']['Node'] = delete_ret
+
+    return ret
+
+
 def cluster_meta_present(name, value, file_name="overrides.yml", cluster="", **kwargs):
     '''
     Ensures that the cluster metadata entry exists
@@ -118,17 +161,27 @@
            'comment': 'Cluster metadata entry "{0}" already exists and is in correct state'.format(name)}
     meta_check = __salt__['reclass.cluster_meta_get'](name, path, **kwargs)
     if not meta_check:
+        if __opts__['test']:
+            ret['result'] = None
+            ret['comment'] = 'Cluster metadata entry "{0}" would be created'.format(name)
+            return ret
         __salt__['reclass.cluster_meta_set'](name, value, path, **kwargs)
-        ret['comment'] = 'Cluster meta entry {0} has been created'.format(name)
+        ret['comment'] = 'Cluster metadata entry {0} has been created'.format(name)
         ret['changes']['Meta Entry'] = 'Cluster meta entry %s: "%s" has been created' % (name, value)
     elif 'Error' in meta_check:
         ret['comment'] = meta_check.get('Error')
         ret['result'] = False
     elif meta_check[name] != value:
+        if __opts__['test']:
+            ret['result'] = None
+            ret['comment'] = 'Cluster metadata entry "{0}" would be updated'.format(name)
+            ret['changes']['Old Meta Entry'] = '{0}: "{1}"'.format(name, meta_check[name])
+            ret['changes']['New Meta Entry'] = '{0}: "{1}"'.format(name, value)
+            return ret
         __salt__['reclass.cluster_meta_set'](name, value, path, **kwargs)
-        ret['comment'] = 'Cluster metadata entry %s has been changed' % (name,)
-        ret['changes']['Old Meta Entry'] = '%s: "%s"' % (name, meta_check[name])
-        ret['changes']['New Meta Entry'] = '%s: "%s"' % (name, value)
+        ret['comment'] = 'Cluster metadata entry {0} has been updated'.format(name)
+        ret['changes']['Old Meta Entry'] = '{0}: "{1}"'.format(name, meta_check[name])
+        ret['changes']['New Meta Entry'] = '{0}: "{1}"'.format(name, value)
     return ret
 
 
@@ -148,6 +201,10 @@
            'comment': 'Cluster metadata entry "{0}" is already absent'.format(name)}
     meta_check = __salt__['reclass.cluster_meta_get'](name, path, **kwargs)
     if meta_check:
+        if __opts__['test']:
+            ret['result'] = None
+            ret['comment'] = 'Cluster metadata entry "{0}" would be deleted'.format(name)
+            return ret
         __salt__['reclass.cluster_meta_delete'](name, path, **kwargs)
         ret['comment'] = 'Cluster metadata entry {0} has been deleted'.format(name)
         ret['changes']['Meta Entry'] = 'Cluster metadata entry %s: "%s" has been deleted' % (name, meta_check[name])
diff --git a/reclass/files/node.yml b/reclass/files/node.yml
index 9b9c3c9..a916432 100644
--- a/reclass/files/node.yml
+++ b/reclass/files/node.yml
@@ -13,10 +13,14 @@
     {%- endif %}
     {%- endfor %}
     {%- for param_name, param_value in extra_params.iteritems() %}
-    {%- if param_value is not string %}
-    {{ param_name }}: {{ param_value }}
+    {%- if param_value.value is not string %}
+    {{ param_name }}: {{ param_value.value }}
     {%- else %}
-    {{ param_name }}: {{ param_value|replace("u'", "'") }}
+    {%- if param_value.interpolate %}
+    {{ param_name }}: {% raw %}'${_param:{% endraw %}{{ param_value.value|replace("u'", "'")|replace("'", "") }}}'
+    {%- else %}
+    {{ param_name }}: {{ param_value.value|replace("u'", "'") }}
+    {%- endif %}
     {%- endif %}
     {%- endfor %}
   {%- endif %}
diff --git a/reclass/map.jinja b/reclass/map.jinja
index 5891a1e..5e3bd19 100644
--- a/reclass/map.jinja
+++ b/reclass/map.jinja
@@ -1,28 +1,20 @@
 {%- set storage = salt['grains.filter_by']({
-    'Debian': {
+    'default': {
         'base_dir': '/etc/reclass/base',
-        'pkgs': ['reclass', 'python-reclass'],
+        'storage_type': 'yaml_fs',
+        'repeat_replace_symbol': '<<count>>',
+        'class_mappings':  {},
+        'ignore_class_notfound': False,
+        'propagate_pillar_data_to_reclass': False,
         'data_source': {
             'engine': 'local'
         },
-        'storage_type': 'yaml_fs',
-        'repeat_replace_symbol': '<<count>>',
         'version': '1.4.1',
-        'class_mappings':  {},
-        'ignore_class_notfound': False,
-        'propagate_pillar_data_to_reclass': False
+    },
+    'Debian': {
+        'pkgs': ['reclass', 'python-reclass'],
     },
     'RedHat': {
-        'base_dir': '/etc/reclass/base',
         'pkgs': ['reclass', 'python-reclass'],
-        'data_source': {
-            'engine': 'local'
-        },
-        'storage_type': 'yaml_fs',
-        'repeat_replace_symbol': '<<count>>',
-        'version': '1.4.1',
-        'class_mappings':  {},
-        'ignore_class_notfound': False,
-        'propagate_pillar_data_to_reclass': False
     },
-}, grain='os_family', merge=salt['pillar.get']('reclass:storage')) %}
+}, grain='os_family', merge=salt['pillar.get']('reclass:storage'), base='default') %}
diff --git a/reclass/storage/node.sls b/reclass/storage/node.sls
index df9bdaa..e3ce328 100644
--- a/reclass/storage/node.sls
+++ b/reclass/storage/node.sls
@@ -12,7 +12,7 @@
 {%- for param_name, param in node.repeat.params.iteritems() %}
 {%- set param_count = (param.get('start', 1) + i)|string %}
 {%- set param_value = param.value|replace(storage.repeat_replace_symbol, param_count.rjust(param.get('digits', 1), '0')) %}
-{%- do extra_params.update({param_name: param_value}) %}
+{%- do extra_params.update({param_name: {'value': param_value, 'interpolate': param.get('interpolate', False)}}) %}
 {%- endfor %}
 
 {%- set node_count = (node.repeat.get('start', 1) + i)|string %}
diff --git a/tests/pillar/generate_multi_interpolate.sls b/tests/pillar/generate_multi_interpolate.sls
new file mode 100644
index 0000000..113aff3
--- /dev/null
+++ b/tests/pillar/generate_multi_interpolate.sls
@@ -0,0 +1,23 @@
+reclass:
+  storage:
+    enabled: true
+    repeat_replace_symbol: <<count>>
+    node:
+      service_node01:
+        name: node<<count>>
+        domain: deployment.local
+        classes:
+        - cluster.deployment.service.role
+        repeat:
+          count: 2
+          start: 5
+          digits: 2
+          params:
+            single_address:
+              value: ceph_osd_node<<count>>_address
+              start: 1
+              digits: 2
+              interpolate: true
+        params:
+          salt_master_host: <<salt-master-ip>>
+          linux_system_codename: trusty