diff --git a/ansible/action_plugins/merge_yaml.py b/ansible/action_plugins/merge_yaml.py
index 371905d0d444b4f5a4aee080c0042f114fb9121e..0bff14dc24c58e48dd2240a5a436ba861e9b3055 100755
--- a/ansible/action_plugins/merge_yaml.py
+++ b/ansible/action_plugins/merge_yaml.py
@@ -32,6 +32,7 @@ except ImportError:
 
 from ansible import constants
 from ansible.plugins import action
+import six
 
 
 class ActionModule(action.ActionBase):
@@ -76,7 +77,7 @@ class ActionModule(action.ActionBase):
         if not isinstance(sources, list):
             sources = [sources]
         for source in sources:
-            output.update(self.read_config(source))
+            Utils.update_nested_conf(output, self.read_config(source))
 
         # restore original vars
         self._templar.set_available_variables(old_vars)
@@ -109,3 +110,14 @@ class ActionModule(action.ActionBase):
         finally:
             shutil.rmtree(local_tempdir)
         return result
+
+
+class Utils(object):
+    @staticmethod
+    def update_nested_conf(conf, update):
+        for k, v in six.iteritems(update):
+            if isinstance(v, dict):
+                conf[k] = Utils.update_nested_conf(conf.get(k, {}), v)
+            else:
+                conf[k] = v
+        return conf
diff --git a/tests/test_merge_yaml.py b/tests/test_merge_yaml.py
new file mode 100644
index 0000000000000000000000000000000000000000..00b56614c4dfea142f19efdbe613400e706d2d69
--- /dev/null
+++ b/tests/test_merge_yaml.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python
+
+# Copyright 2018 StackHPC Ltd.
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import imp
+import os
+
+from oslotest import base
+
+PROJECT_DIR = os.path.abspath(os.path.join(os. path.dirname(__file__), '../'))
+MERGE_YAML_FILE = os.path.join(PROJECT_DIR,
+                               'ansible/action_plugins/merge_yaml.py')
+
+merge_yaml = imp.load_source('merge_yaml', MERGE_YAML_FILE)
+
+
+class MergeYamlConfigTest(base.BaseTestCase):
+
+    def test_merge_no_update(self):
+        initial_conf = {
+            'foo': 'bar',
+            'egg': 'spam'
+        }
+        actual = merge_yaml.Utils.update_nested_conf(initial_conf, {})
+        expected = {
+            'foo': 'bar',
+            'egg': 'spam'
+        }
+        self.assertDictEqual(actual, expected)
+
+    def test_merge_flat_update_key(self):
+        initial_conf = {
+            'foo': 'bar',
+            'egg': 'spam'
+        }
+        actual = merge_yaml.Utils.update_nested_conf(
+            initial_conf, {'egg': 'ham'})
+        expected = {
+            'foo': 'bar',
+            'egg': 'ham'
+        }
+        self.assertDictEqual(actual, expected)
+
+    def test_merge_flat_new_key(self):
+        initial_conf = {
+            'foo': 'bar',
+            'egg': 'spam'
+        }
+        actual = merge_yaml.Utils.update_nested_conf(
+            initial_conf, {'spam': 'ham'})
+        expected = {
+            'foo': 'bar',
+            'egg': 'spam',
+            'spam': 'ham'
+        }
+        self.assertDictEqual(actual, expected)
+
+    def test_merge_nested_update_key(self):
+        initial_conf = {
+            'foo': {
+                'a': 'b',
+            },
+            'bar': {
+                'a': False,
+                'b': 'INFO'
+            }
+        }
+        actual = merge_yaml.Utils.update_nested_conf(
+            initial_conf, {'bar': {'a': True}})
+        expected = {
+            'foo': {
+                'a': 'b',
+            },
+            'bar': {
+                'a': True,
+                'b': 'INFO'
+            }
+        }
+        self.assertDictEqual(actual, expected)
+
+    def test_merge_nested_new_key(self):
+        initial_conf = {
+            'foo': {
+                'a': 'b',
+                'c': 30
+            }
+        }
+        actual = merge_yaml.Utils.update_nested_conf(
+            initial_conf, {'egg': {'spam': 10}})
+        expected = {
+            'foo': {
+                'a': 'b',
+                'c': 30,
+            },
+            'egg': {
+                'spam': 10,
+            }
+        }
+        self.assertDictEqual(actual, expected)
+
+    def test_merge_nested_new_nested_key(self):
+        initial_conf = {
+            'foo': {
+                'a': 'b',
+                'c': 30
+            }
+        }
+        actual = merge_yaml.Utils.update_nested_conf(
+            initial_conf, {'foo': {'spam': 10}})
+        expected = {
+            'foo': {
+                'a': 'b',
+                'c': 30,
+                'spam': 10,
+            }
+        }
+        self.assertDictEqual(actual, expected)