Blame SOURCES/storage-safemode.diff

47f324
diff --git a/README.md b/README.md
47f324
index c2debc9..f808adc 100644
47f324
--- a/README.md
47f324
+++ b/README.md
47f324
@@ -73,6 +73,9 @@ The `mount_point` specifies the directory on which the file system will be mount
47f324
 ##### `mount_options`
47f324
 The `mount_options` specifies custom mount options as a string, e.g.: 'ro'.
47f324
 
47f324
+#### `storage_safe_mode`
47f324
+When true (the default), an error will occur instead of automatically removing existing devices and/or formatting.
47f324
+
47f324
 
47f324
 Example Playbook
47f324
 ----------------
47f324
diff --git a/defaults/main.yml b/defaults/main.yml
47f324
index 7b500e5..476616b 100644
47f324
--- a/defaults/main.yml
47f324
+++ b/defaults/main.yml
47f324
@@ -3,6 +3,7 @@
47f324
 storage_provider: "blivet"
47f324
 storage_use_partitions: null
47f324
 storage_disklabel_type: null  # leave unset to allow the role to select an appropriate label type
47f324
+storage_safe_mode: true  # fail instead of implicitly/automatically removing devices or formatting
47f324
 
47f324
 storage_pool_defaults:
47f324
   state: "present"
47f324
diff --git a/library/blivet.py b/library/blivet.py
47f324
index d416944..1d8cd36 100644
47f324
--- a/library/blivet.py
47f324
+++ b/library/blivet.py
47f324
@@ -31,6 +31,9 @@ options:
47f324
     disklabel_type:
47f324
         description:
47f324
             - disklabel type string (eg: 'gpt') to use, overriding the built-in logic in blivet
47f324
+    safe_mode:
47f324
+        description:
47f324
+            - boolean indicating that we should fail rather than implicitly/automatically removing devices or formatting
47f324
 
47f324
 author:
47f324
     - David Lehman (dlehman@redhat.com)
47f324
@@ -112,13 +115,15 @@ if BLIVET_PACKAGE:
47f324
 
47f324
 use_partitions = None  # create partitions on pool backing device disks?
47f324
 disklabel_type = None  # user-specified disklabel type
47f324
+safe_mode = None       # do not remove any existing devices or formatting
47f324
+packages_only = None   # only set things up enough to get a list of required packages
47f324
 
47f324
 
47f324
 class BlivetAnsibleError(Exception):
47f324
     pass
47f324
 
47f324
 
47f324
-class BlivetVolume:
47f324
+class BlivetVolume(object):
47f324
     def __init__(self, blivet_obj, volume, bpool=None):
47f324
         self._blivet = blivet_obj
47f324
         self._volume = volume
47f324
@@ -206,11 +211,16 @@ class BlivetVolume:
47f324
 
47f324
     def _reformat(self):
47f324
         """ Schedule actions as needed to ensure the volume is formatted as specified. """
47f324
+        global packages_only
47f324
+
47f324
         fmt = self._get_format()
47f324
         if self._device.format.type == fmt.type:
47f324
             return
47f324
 
47f324
-        if self._device.format.status:
47f324
+        if safe_mode and (self._device.format.type is not None or self._device.format.name != get_format(None).name):
47f324
+            raise BlivetAnsibleError("cannot remove existing formatting on volume '%s' in safe mode" % self._volume['name'])
47f324
+
47f324
+        if self._device.format.status and not packages_only:
47f324
             self._device.format.teardown()
47f324
         self._blivet.format_device(self._device, fmt)
47f324
 
47f324
@@ -251,6 +261,19 @@ class BlivetDiskVolume(BlivetVolume):
47f324
     def _type_check(self):
47f324
         return self._device.is_disk
47f324
 
47f324
+    def _look_up_device(self):
47f324
+        super(BlivetDiskVolume, self)._look_up_device()
47f324
+        if not self._get_device_id():
47f324
+            # FAIL: no disks specified for volume
47f324
+            raise BlivetAnsibleError("no disks specified for volume '%s'" % self._volume['name'])  # sure about this one?
47f324
+        elif not isinstance(self._volume['disks'], list):
47f324
+            raise BlivetAnsibleError("volume disks must be specified as a list")
47f324
+
47f324
+        if self._device is None:
47f324
+            # FAIL: failed to find the disk
47f324
+            raise BlivetAnsibleError("unable to resolve disk specified for volume '%s' (%s)" % (self._volume['name'], self._volume['disks']))
47f324
+
47f324
+
47f324
 
47f324
 class BlivetPartitionVolume(BlivetVolume):
47f324
     def _type_check(self):
47f324
@@ -342,7 +365,7 @@ def _get_blivet_volume(blivet_obj, volume, bpool=None):
47f324
     return _BLIVET_VOLUME_TYPES[volume_type](blivet_obj, volume, bpool=bpool)
47f324
 
47f324
 
47f324
-class BlivetPool:
47f324
+class BlivetPool(object):
47f324
     def __init__(self, blivet_obj, pool):
47f324
         self._blivet = blivet_obj
47f324
         self._pool = pool
47f324
@@ -424,8 +447,11 @@ class BlivetPool:
47f324
         """ Schedule actions as needed to ensure pool member devices exist. """
47f324
         members = list()
47f324
         for disk in self._disks:
47f324
-            if not disk.isleaf:
47f324
-                self._blivet.devicetree.recursive_remove(disk)
47f324
+            if not disk.isleaf or disk.format.type is not None:
47f324
+                if not safe_mode:
47f324
+                    self._blivet.devicetree.recursive_remove(disk)
47f324
+                else:
47f324
+                    raise BlivetAnsibleError("cannot remove existing formatting and/or devices on disk '%s' (pool '%s') in safe mode" % (disk.name, self._pool['name']))
47f324
 
47f324
             if use_partitions:
47f324
                 label = get_format("disklabel", device=disk.path)
47f324
@@ -486,7 +512,10 @@ class BlivetPartitionPool(BlivetPool):
47f324
     def _create(self):
47f324
         if self._device.format.type != "disklabel" or \
47f324
            self._device.format.label_type != disklabel_type:
47f324
-            self._blivet.devicetree.recursive_remove(self._device, remove_device=False)
47f324
+            if not safe_mode:
47f324
+                self._blivet.devicetree.recursive_remove(self._device, remove_device=False)
47f324
+            else:
47f324
+                raise BlivetAnsibleError("cannot remove existing formatting and/or devices on disk '%s' (pool '%s') in safe mode" % (self._device.name, self._pool['name']))
47f324
 
47f324
             label = get_format("disklabel", device=self._device.path, label_type=disklabel_type)
47f324
             self._blivet.format_device(self._device, label)
47f324
@@ -520,7 +549,7 @@ class BlivetLVMPool(BlivetPool):
47f324
 
47f324
 
47f324
 _BLIVET_POOL_TYPES = {
47f324
-    "disk": BlivetPartitionPool,
47f324
+    "partition": BlivetPartitionPool,
47f324
     "lvm": BlivetLVMPool
47f324
 }
47f324
 
47f324
@@ -550,7 +579,7 @@ def manage_pool(b, pool):
47f324
         volume['_mount_id'] = bvolume._volume.get('_mount_id', '')
47f324
 
47f324
 
47f324
-class FSTab:
47f324
+class FSTab(object):
47f324
     def __init__(self, blivet_obj):
47f324
         self._blivet = blivet_obj
47f324
         self._entries = list()
47f324
@@ -656,6 +685,7 @@ def run_module():
47f324
         volumes=dict(type='list'),
47f324
         packages_only=dict(type='bool', required=False, default=False),
47f324
         disklabel_type=dict(type='str', required=False, default=None),
47f324
+        safe_mode=dict(type='bool', required=False, default=False),
47f324
         use_partitions=dict(type='bool', required=False, default=True))
47f324
 
47f324
     # seed the result dict in the object
47f324
@@ -684,6 +714,12 @@ def run_module():
47f324
     global use_partitions
47f324
     use_partitions = module.params['use_partitions']
47f324
 
47f324
+    global safe_mode
47f324
+    safe_mode = module.params['safe_mode']
47f324
+
47f324
+    global packages_only
47f324
+    packages_only = module.params['packages_only']
47f324
+
47f324
     b = Blivet()
47f324
     b.reset()
47f324
     fstab = FSTab(b)
47f324
diff --git a/tasks/main-blivet.yml b/tasks/main-blivet.yml
47f324
index 061195c..65b8580 100644
47f324
--- a/tasks/main-blivet.yml
47f324
+++ b/tasks/main-blivet.yml
47f324
@@ -38,7 +38,7 @@
47f324
     _storage_vols_no_defaults: "{{ _storage_vols_no_defaults|default([]) }} + [{{ item.1 }}]"
47f324
     _storage_vol_defaults: "{{ _storage_vol_defaults|default([]) }} + [{{ storage_volume_defaults }}]"
47f324
     _storage_vol_pools: "{{ _storage_vol_pools|default([]) }} + ['{{ item.0.name }}']"
47f324
-  loop: "{{ _storage_pools|subelements('volumes') }}"
47f324
+  loop: "{{ _storage_pools|subelements('volumes', skip_missing=true) }}"
47f324
   when: storage_pools is defined
47f324
 
47f324
 - name: Apply defaults to pools and volumes [3/6]
47f324
@@ -85,6 +85,15 @@
47f324
 - debug:
47f324
     var: _storage_volumes
47f324
 
47f324
+- name: load mount facts
47f324
+  setup:
47f324
+    gather_subset: '!all,!min,mounts'
47f324
+  register: __storage_mounts_before_packages
47f324
+
47f324
+# - name: show mounts before get required packages
47f324
+#   debug:
47f324
+#     var: __storage_mounts_before_packages
47f324
+
47f324
 - name: get required packages
47f324
   blivet:
47f324
     pools: "{{ _storage_pools }}"
47f324
@@ -94,6 +103,30 @@
47f324
     packages_only: true
47f324
   register: package_info
47f324
 
47f324
+- name: load mount facts
47f324
+  setup:
47f324
+    gather_subset: '!all,!min,mounts'
47f324
+  register: __storage_mounts_after_packages
47f324
+
47f324
+- name: detect mount alteration by 'get required packages'
47f324
+  block:
47f324
+    - name: show mounts before manage the pools and volumes
47f324
+      debug:
47f324
+        var: __storage_mounts_before_packages.ansible_facts.ansible_mounts
47f324
+
47f324
+    - name: show mounts after manage the pools and volumes
47f324
+      debug:
47f324
+        var: __storage_mounts_after_packages.ansible_facts.ansible_mounts
47f324
+
47f324
+    - name: fail if mounts changed
47f324
+      fail:
47f324
+        msg: "get required packages changed mounts. Changed status is
47f324
+      {{ package_info.changed }}"
47f324
+  when:
47f324
+    - __storage_mounts_before_packages.ansible_facts.ansible_mounts |
47f324
+      count !=
47f324
+      __storage_mounts_after_packages.ansible_facts.ansible_mounts | count
47f324
+
47f324
 - name: make sure required packages are installed
47f324
   package:
47f324
     name: "{{ package_info.packages }}"
47f324
@@ -105,6 +138,7 @@
47f324
     volumes: "{{ _storage_volumes }}"
47f324
     use_partitions: "{{ storage_use_partitions }}"
47f324
     disklabel_type: "{{ storage_disklabel_type }}"
47f324
+    safe_mode: "{{ storage_safe_mode }}"
47f324
   register: blivet_output
47f324
 
47f324
 - debug:
47f324
diff --git a/tests/get_unused_disk.yml b/tests/get_unused_disk.yml
47f324
index 9f4c5d2..79e952a 100644
47f324
--- a/tests/get_unused_disk.yml
47f324
+++ b/tests/get_unused_disk.yml
47f324
@@ -9,12 +9,10 @@
47f324
     unused_disks: "{{ unused_disks_return.disks }}"
47f324
   when: "'Unable to find unused disk' not in unused_disks_return.disks"
47f324
 
47f324
-- block:
47f324
-    - name: Exit playbook when there's no unused disks in the system
47f324
-      debug:
47f324
-        msg: "Unable to find unused disks. Exiting playbook."
47f324
-    - meta: end_play
47f324
-  when: unused_disks is undefined
47f324
+- name: Exit playbook when there's not enough unused disks in the system
47f324
+  fail:
47f324
+    msg: "Unable to find enough unused disks. Exiting playbook."
47f324
+  when: unused_disks is undefined or unused_disks|length < disks_needed|default(1)
47f324
 
47f324
 - name: Print unused disks
47f324
   debug:
47f324
diff --git a/tests/tests_change_disk_fs.yml b/tests/tests_change_disk_fs.yml
47f324
index b6aa80b..f7962c6 100644
47f324
--- a/tests/tests_change_disk_fs.yml
47f324
+++ b/tests/tests_change_disk_fs.yml
47f324
@@ -2,6 +2,7 @@
47f324
 - hosts: all
47f324
   become: true
47f324
   vars:
47f324
+    storage_safe_mode: false
47f324
     mount_location: '/opt/test'
47f324
     volume_size: '5g'
47f324
     fs_type_after: "{{ 'ext3' if (ansible_distribution == 'RedHat' and ansible_distribution_major_version == '6') else 'ext4' }}"
47f324
diff --git a/tests/tests_change_fs.yml b/tests/tests_change_fs.yml
47f324
index cca23eb..b88e768 100644
47f324
--- a/tests/tests_change_fs.yml
47f324
+++ b/tests/tests_change_fs.yml
47f324
@@ -2,6 +2,7 @@
47f324
 - hosts: all
47f324
   become: true
47f324
   vars:
47f324
+    storage_safe_mode: false
47f324
     mount_location: '/opt/test1'
47f324
     volume_size: '5g'
47f324
     fs_after: "{{ (ansible_distribution == 'RedHat' and ansible_distribution_major_version == '6') | ternary('ext4', 'xfs') }}"
47f324
diff --git a/tests/tests_change_fs_use_partitions.yml b/tests/tests_change_fs_use_partitions.yml
47f324
index e4aa76c..eb93c11 100644
47f324
--- a/tests/tests_change_fs_use_partitions.yml
47f324
+++ b/tests/tests_change_fs_use_partitions.yml
47f324
@@ -2,6 +2,7 @@
47f324
 - hosts: all
47f324
   become: true
47f324
   vars:
47f324
+    storage_safe_mode: false
47f324
     storage_use_partitions: true
47f324
     mount_location: '/opt/test1'
47f324
     volume_size: '5g'
47f324
diff --git a/tests/tests_create_disk_then_remove.yml b/tests/tests_create_disk_then_remove.yml
47f324
index b19ae35..c5290eb 100644
47f324
--- a/tests/tests_create_disk_then_remove.yml
47f324
+++ b/tests/tests_create_disk_then_remove.yml
47f324
@@ -2,6 +2,7 @@
47f324
 - hosts: all
47f324
   become: true
47f324
   vars:
47f324
+    storage_safe_mode: false
47f324
     mount_location: '/opt/test1'
47f324
 
47f324
   tasks:
47f324
diff --git a/tests/tests_create_lvm_pool_then_remove.yml b/tests/tests_create_lvm_pool_then_remove.yml
47f324
index 6b25939..f2c06fb 100644
47f324
--- a/tests/tests_create_lvm_pool_then_remove.yml
47f324
+++ b/tests/tests_create_lvm_pool_then_remove.yml
47f324
@@ -2,6 +2,7 @@
47f324
 - hosts: all
47f324
   become: true
47f324
   vars:
47f324
+    storage_safe_mode: false
47f324
     mount_location1: '/opt/test1'
47f324
     mount_location2: '/opt/test2'
47f324
     volume_group_size: '10g'
47f324
diff --git a/tests/tests_create_partition_volume_then_remove.yml b/tests/tests_create_partition_volume_then_remove.yml
47f324
index 40b3e62..ae589d3 100644
47f324
--- a/tests/tests_create_partition_volume_then_remove.yml
47f324
+++ b/tests/tests_create_partition_volume_then_remove.yml
47f324
@@ -2,6 +2,7 @@
47f324
 - hosts: all
47f324
   become: true
47f324
   vars:
47f324
+    storage_safe_mode: false
47f324
     mount_location: '/opt/test1'
47f324
 
47f324
   tasks:
47f324
@@ -18,7 +19,7 @@
47f324
       vars:
47f324
         storage_pools:
47f324
           - name: "{{ unused_disks[0] }}"
47f324
-            type: disk
47f324
+            type: partition
47f324
             disks: "{{ unused_disks }}"
47f324
             volumes:
47f324
               - name: test1
47f324
@@ -33,7 +34,7 @@
47f324
       vars:
47f324
         storage_pools:
47f324
           - name: "{{ unused_disks[0] }}"
47f324
-            type: disk
47f324
+            type: partition
47f324
             disks: "{{ unused_disks }}"
47f324
             volumes:
47f324
               - name: test1
47f324
@@ -48,7 +49,7 @@
47f324
       vars:
47f324
         storage_pools:
47f324
           - name:  "{{ unused_disks[0] }}"
47f324
-            type: disk
47f324
+            type: partition
47f324
             disks: "{{ unused_disks }}"
47f324
             state: absent
47f324
             volumes:
47f324
@@ -65,7 +66,7 @@
47f324
       vars:
47f324
         storage_pools:
47f324
           - name:  "{{ unused_disks[0] }}"
47f324
-            type: disk
47f324
+            type: partition
47f324
             disks: "{{ unused_disks }}"
47f324
             state: absent
47f324
             volumes:
47f324
diff --git a/tests/tests_disk_errors.yml b/tests/tests_disk_errors.yml
47f324
index 36eec41..7112f6e 100644
47f324
--- a/tests/tests_disk_errors.yml
47f324
+++ b/tests/tests_disk_errors.yml
47f324
@@ -3,8 +3,17 @@
47f324
   become: true
47f324
   vars:
47f324
     mount_location: '/opt/test1'
47f324
+    testfile: "{{ mount_location }}/quux"
47f324
 
47f324
   tasks:
47f324
+    - include_role:
47f324
+        name: storage
47f324
+
47f324
+    - include_tasks: get_unused_disk.yml
47f324
+      vars:
47f324
+        min_size: "10g"
47f324
+        max_return: 1
47f324
+
47f324
     - name: Verify that the play fails with the expected error message
47f324
       block:
47f324
         - name: Create a disk volume mounted at "{{ mount_location }}"
47f324
@@ -14,11 +23,246 @@
47f324
             storage_volumes:
47f324
               - name: test1
47f324
                 type: disk
47f324
-                disks: "['/dev/surelyidonotexist']"
47f324
+                disks: ['/dev/surelyidonotexist']
47f324
                 mount_point: "{{ mount_location }}"
47f324
 
47f324
-        - name: Check the error output
47f324
+        - name: UNREACH
47f324
+          fail:
47f324
+            msg: "this should be unreachable"
47f324
+
47f324
+      rescue:
47f324
+        - name: Check that we failed in the role
47f324
+          assert:
47f324
+            that:
47f324
+              - ansible_failed_task.name != 'UNREACH'
47f324
+            msg: "Role has not failed when it should have"
47f324
+          vars:
47f324
+            # Ugh! needed to expand ansible_failed_task
47f324
+            storage_provider: blivet
47f324
+
47f324
+        # the following does not work properly,
47f324
+        # blivet_output.failed is false.
47f324
+        # - name: Show the error output
47f324
+        #   debug:
47f324
+        #     msg: "{{ blivet_output.failed }}"
47f324
+
47f324
+        # - name: Check the error output
47f324
+        #   assert:
47f324
+        #     that: blivet_output.failed | bool
47f324
+        #     msg: "Expected error message not found for missing disk"
47f324
+
47f324
+    - name: Create a file system on disk
47f324
+      include_role:
47f324
+        name: storage
47f324
+      vars:
47f324
+        storage_volumes:
47f324
+          - name: test1
47f324
+            type: disk
47f324
+            fs_type: 'ext4'
47f324
+            disks: "{{ unused_disks }}"
47f324
+            mount_point: "{{ mount_location }}"
47f324
+
47f324
+    - name: create a file
47f324
+      file:
47f324
+        path: "{{ testfile }}"
47f324
+        state: touch
47f324
+
47f324
+    - name: Test for correct handling of safe_mode
47f324
+      block:
47f324
+        - name: Try to replace the file system on disk in safe mode
47f324
+          include_role:
47f324
+            name: storage
47f324
+          vars:
47f324
+            storage_volumes:
47f324
+              - name: test1
47f324
+                type: disk
47f324
+                fs_type: 'ext3'
47f324
+                disks: "{{ unused_disks }}"
47f324
+
47f324
+        - name: UNREACH
47f324
+          fail:
47f324
+            msg: "this should be unreachable"
47f324
+
47f324
+      rescue:
47f324
+        - name: Check that we failed in the role
47f324
+          assert:
47f324
+            that:
47f324
+              - ansible_failed_task.name != 'UNREACH'
47f324
+            msg: "Role has not failed when it should have"
47f324
+          vars:
47f324
+            # Ugh! needed to expand ansible_failed_task
47f324
+            storage_provider: blivet
47f324
+
47f324
+    - name: Verify the output
47f324
+      assert:
47f324
+        that: "blivet_output.failed and
47f324
+               blivet_output.msg|regex_search('cannot remove existing formatting on volume.*in safe mode') and
47f324
+                  not blivet_output.changed"
47f324
+        msg: "Unexpected behavior w/ existing data on specified disks"
47f324
+
47f324
+    - name: Unmount file system
47f324
+      include_role:
47f324
+        name: storage
47f324
+      vars:
47f324
+        storage_volumes:
47f324
+          - name: test1
47f324
+            type: disk
47f324
+            fs_type: 'ext4'
47f324
+            disks: "{{ unused_disks }}"
47f324
+            mount_point: none
47f324
+
47f324
+    - name: Test for correct handling of safe_mode with unmounted filesystem
47f324
+      block:
47f324
+        - name: Try to replace the file system on disk in safe mode
47f324
+          include_role:
47f324
+            name: storage
47f324
+          vars:
47f324
+            storage_volumes:
47f324
+              - name: test1
47f324
+                type: disk
47f324
+                fs_type: 'ext3'
47f324
+                disks: "{{ unused_disks }}"
47f324
+
47f324
+        - name: UNREACH
47f324
+          fail:
47f324
+            msg: "this should be unreachable"
47f324
+
47f324
+      rescue:
47f324
+        - name: Check that we failed in the role
47f324
           assert:
47f324
-            that: "{{ blivet_output.failed }}"
47f324
-            msg: "Expected error message not found for missing disk"
47f324
-      ignore_errors: yes
47f324
+            that:
47f324
+              - ansible_failed_task.name != 'UNREACH'
47f324
+            msg: "Role has not failed when it should have"
47f324
+          vars:
47f324
+            # Ugh! needed to expand ansible_failed_task
47f324
+            storage_provider: blivet
47f324
+
47f324
+    - name: Verify the output
47f324
+      assert:
47f324
+        that: "blivet_output.failed and
47f324
+               blivet_output.msg|regex_search('cannot remove existing formatting on volume.*in safe mode') and
47f324
+                  not blivet_output.changed"
47f324
+        msg: "Unexpected behavior w/ existing data on specified disks"
47f324
+
47f324
+    - name: Remount file system
47f324
+      include_role:
47f324
+        name: storage
47f324
+      vars:
47f324
+        storage_volumes:
47f324
+          - name: test1
47f324
+            type: disk
47f324
+            fs_type: 'ext4'
47f324
+            disks: "{{ unused_disks }}"
47f324
+            mount_point: "{{ mount_location }}"
47f324
+
47f324
+    - name: stat the file
47f324
+      stat:
47f324
+        path: "{{ testfile }}"
47f324
+      register: stat_r
47f324
+
47f324
+    - name: assert file presence
47f324
+      assert:
47f324
+        that:
47f324
+          stat_r.stat.isreg is defined and stat_r.stat.isreg
47f324
+        msg: "data lost!"
47f324
+
47f324
+    - name: Test for correct handling of safe_mode
47f324
+      block:
47f324
+        - name: Try to create a partition pool on the disk already containing a file system in safe_mode
47f324
+          include_role:
47f324
+            name: storage
47f324
+          vars:
47f324
+            storage_pools:
47f324
+              - name: foo
47f324
+                disks: "{{ unused_disks }}"
47f324
+                type: partition
47f324
+
47f324
+        - name: UNREACH
47f324
+          fail:
47f324
+            msg: "this should be unreachable"
47f324
+
47f324
+      rescue:
47f324
+        - name: Check that we failed in the role
47f324
+          assert:
47f324
+            that:
47f324
+              - ansible_failed_task.name != 'UNREACH'
47f324
+            msg: "Role has not failed when it should have"
47f324
+          vars:
47f324
+            # Ugh! needed to expand ansible_failed_task
47f324
+            storage_provider: blivet
47f324
+
47f324
+        - name: Verify the output
47f324
+          assert:
47f324
+            that: "blivet_output.failed and
47f324
+                   blivet_output.msg|regex_search('cannot remove existing formatting and/or devices on disk.*in safe mode') and
47f324
+                   not blivet_output.changed"
47f324
+            msg: "Unexpected behavior w/ existing data on specified disks"
47f324
+
47f324
+    - name: Test for correct handling of safe_mode with existing filesystem
47f324
+      block:
47f324
+        - name: Try to create LVM pool on disk that already belongs to an existing filesystem
47f324
+          include_role:
47f324
+            name: storage
47f324
+          vars:
47f324
+            storage_pools:
47f324
+              - name: foo
47f324
+                disks: "{{ unused_disks }}"
47f324
+                type: lvm
47f324
+
47f324
+        - name: UNREACH
47f324
+          fail:
47f324
+            msg: "this should be unreachable"
47f324
+
47f324
+      rescue:
47f324
+        - name: Check that we failed in the role
47f324
+          assert:
47f324
+            that:
47f324
+              - ansible_failed_task.name != 'UNREACH'
47f324
+            msg: "Role has not failed when it should have"
47f324
+          vars:
47f324
+            # Ugh! needed to expand ansible_failed_task
47f324
+            storage_provider: blivet
47f324
+
47f324
+        - name: Verify the output
47f324
+          assert:
47f324
+            that: "{{ blivet_output.failed and
47f324
+                      blivet_output.msg|regex_search('cannot remove existing formatting and/or devices on disk.*in safe mode') and
47f324
+                      not blivet_output.changed }}"
47f324
+            msg: "Unexpected behavior w/ existing data on specified disks"
47f324
+
47f324
+    - name: stat the file
47f324
+      stat:
47f324
+        path: "{{ testfile }}"
47f324
+      register: stat_r
47f324
+
47f324
+    - name: assert file presence
47f324
+      assert:
47f324
+        that:
47f324
+          stat_r.stat.isreg is defined and stat_r.stat.isreg
47f324
+        msg: "data lost!"
47f324
+
47f324
+    - name: Create a partition pool on the disk already containing a file system w/o safe_mode
47f324
+      include_role:
47f324
+        name: storage
47f324
+      vars:
47f324
+        storage_safe_mode: false
47f324
+        storage_pools:
47f324
+          - name: foo
47f324
+            disks: "{{ unused_disks }}"
47f324
+            type: partition
47f324
+
47f324
+    - name: Verify the output
47f324
+      assert:
47f324
+        that: not blivet_output.failed
47f324
+        msg: "failed to create partition pool over existing file system w/o safe_mode"
47f324
+
47f324
+    - name: Clean up
47f324
+      include_role:
47f324
+        name: storage
47f324
+      vars:
47f324
+        storage_safe_mode: false
47f324
+        storage_pools:
47f324
+          - name: foo
47f324
+            type: partition
47f324
+            disks: "{{ unused_disks }}"
47f324
+            state: absent
47f324
diff --git a/tests/tests_lvm_errors.yml b/tests/tests_lvm_errors.yml
47f324
index ab23674..e8be153 100644
47f324
--- a/tests/tests_lvm_errors.yml
47f324
+++ b/tests/tests_lvm_errors.yml
47f324
@@ -33,13 +33,32 @@
47f324
                     size: "{{ volume1_size }}"
47f324
                     mount_point: "{{ mount_location1 }}"
47f324
 
47f324
-        - name: Verify the output
47f324
+        - name: UNREACH
47f324
+          fail:
47f324
+            msg: "this should be unreachable"
47f324
+
47f324
+      rescue:
47f324
+        - name: Check that we failed in the role
47f324
           assert:
47f324
-            that: "{{ blivet_output.failed and
47f324
-                      blivet_output.msg|regex_search('unable to resolve.+disk')|length>0 and
47f324
-                      not blivet_output.changed }}"
47f324
-            msg: "Unexpected behavior w/ non-existent pool disk"
47f324
-      ignore_errors: yes
47f324
+            that:
47f324
+              - ansible_failed_task.name != 'UNREACH'
47f324
+            msg: "Role has not failed when it should have"
47f324
+          vars:
47f324
+            # Ugh! needed to expand ansible_failed_task
47f324
+            storage_provider: blivet
47f324
+
47f324
+    # the following does not work properly
47f324
+    # - debug:
47f324
+    #     msg: "{{ 'failed: ' + blivet_output.failed | string +
47f324
+    #              'msg: ' + blivet_output.msg +
47f324
+    #              'changed: ' + blivet_output.changed | string }}"
47f324
+
47f324
+    # - name: Verify the output
47f324
+    #   assert:
47f324
+    #     that: "{{ blivet_output.failed and
47f324
+    #               blivet_output.msg|regex_search('unable to resolve.+disk')|length>0 and
47f324
+    #               not blivet_output.changed }}"
47f324
+    #     msg: "Unexpected behavior w/ non-existent pool disk"
47f324
 
47f324
     - name: Test for correct handling of invalid size specification.
47f324
       block:
47f324
@@ -55,13 +74,27 @@
47f324
                     size: "{{ invalid_size }}"
47f324
                     mount_point: "{{ mount_location1 }}"
47f324
 
47f324
-        - name: Verify the output
47f324
+        - name: UNREACH
47f324
+          fail:
47f324
+            msg: "this should be unreachable"
47f324
+
47f324
+      rescue:
47f324
+        - name: Check that we failed in the role
47f324
           assert:
47f324
-            that: "{{ blivet_output.failed and
47f324
-                      blivet_output.msg|regex_search('invalid size.+for volume') and
47f324
-                      not blivet_output.changed }}"
47f324
-            msg: "Unexpected behavior w/ invalid volume size"
47f324
-      ignore_errors: yes
47f324
+            that:
47f324
+              - ansible_failed_task.name != 'UNREACH'
47f324
+            msg: "Role has not failed when it should have"
47f324
+          vars:
47f324
+            # Ugh! needed to expand ansible_failed_task
47f324
+            storage_provider: blivet
47f324
+
47f324
+    # the following does not work properly
47f324
+    # - name: Verify the output
47f324
+    #   assert:
47f324
+    #     that: "{{ blivet_output.failed and
47f324
+    #               blivet_output.msg|regex_search('invalid size.+for volume') and
47f324
+    #               not blivet_output.changed }}"
47f324
+    #     msg: "Unexpected behavior w/ invalid volume size"
47f324
 
47f324
     - name: Test for correct handling of too-large volume size.
47f324
       block:
47f324
@@ -77,13 +110,27 @@
47f324
                     size: "{{ too_large_size }}"
47f324
                     mount_point: "{{ mount_location1 }}"
47f324
 
47f324
-        - name: Verify the output
47f324
+        - name: UNREACH
47f324
+          fail:
47f324
+            msg: "this should be unreachable"
47f324
+
47f324
+      rescue:
47f324
+        - name: Check that we failed in the role
47f324
           assert:
47f324
-            that: "{{ blivet_output.failed and
47f324
-                      blivet_output.msg|regex_search('size.+exceeds.+space in pool') and
47f324
-                      not blivet_output.changed }}"
47f324
-            msg: "Unexpected behavior w/ too-large volume size"
47f324
-      ignore_errors: yes
47f324
+            that:
47f324
+              - ansible_failed_task.name != 'UNREACH'
47f324
+            msg: "Role has not failed when it should have"
47f324
+          vars:
47f324
+            # Ugh! needed to expand ansible_failed_task
47f324
+            storage_provider: blivet
47f324
+
47f324
+    # the following does not work properly
47f324
+    # - name: Verify the output
47f324
+    #   assert:
47f324
+    #     that: "{{ blivet_output.failed and
47f324
+    #               blivet_output.msg|regex_search('size.+exceeds.+space in pool') and
47f324
+    #               not blivet_output.changed }}"
47f324
+    #     msg: "Unexpected behavior w/ too-large volume size"
47f324
 
47f324
     - name: Test for correct handling of non-list disk specification.
47f324
       block:
47f324
@@ -99,13 +146,27 @@
47f324
                     size: "{{ too_large_size }}"
47f324
                     mount_point: "{{ mount_location1 }}"
47f324
 
47f324
-        - name: Verify the output
47f324
+        - name: UNREACH
47f324
+          fail:
47f324
+            msg: "this should be unreachable"
47f324
+
47f324
+      rescue:
47f324
+        - name: Check that we failed in the role
47f324
           assert:
47f324
-            that: "{{ blivet_output.failed and
47f324
-                      blivet_output.msg|regex_search('disk.+list') and
47f324
-                      not blivet_output.changed }}"
47f324
-            msg: "Unexpected behavior w/ disks not in list form"
47f324
-      ignore_errors: yes
47f324
+            that:
47f324
+              - ansible_failed_task.name != 'UNREACH'
47f324
+            msg: "Role has not failed when it should have"
47f324
+          vars:
47f324
+            # Ugh! needed to expand ansible_failed_task
47f324
+            storage_provider: blivet
47f324
+
47f324
+    # the following does not work properly
47f324
+    # - name: Verify the output
47f324
+    #   assert:
47f324
+    #     that: "{{ blivet_output.failed and
47f324
+    #               blivet_output.msg|regex_search('disk.+list') and
47f324
+    #               not blivet_output.changed }}"
47f324
+    #     msg: "Unexpected behavior w/ disks not in list form"
47f324
 
47f324
     - name: Test for correct handling of missing disk specification.
47f324
       block:
47f324
@@ -121,13 +182,27 @@
47f324
                     size: "{{ too_large_size }}"
47f324
                     mount_point: "{{ mount_location1 }}"
47f324
 
47f324
-        - name: Verify the output
47f324
+        - name: UNREACH
47f324
+          fail:
47f324
+            msg: "this should be unreachable"
47f324
+
47f324
+      rescue:
47f324
+        - name: Check that we failed in the role
47f324
           assert:
47f324
-            that: "{{ blivet_output.failed and
47f324
-                      blivet_output.msg|regex_search('no disks.+pool') and
47f324
-                      not blivet_output.changed }}"
47f324
-            msg: "Unexpected behavior w/ no disks specified"
47f324
-      ignore_errors: yes
47f324
+            that:
47f324
+              - ansible_failed_task.name != 'UNREACH'
47f324
+            msg: "Role has not failed when it should have"
47f324
+          vars:
47f324
+            # Ugh! needed to expand ansible_failed_task
47f324
+            storage_provider: blivet
47f324
+
47f324
+    # the following does not work properly
47f324
+    # - name: Verify the output
47f324
+    #   assert:
47f324
+    #     that: "{{ blivet_output.failed and
47f324
+    #               blivet_output.msg|regex_search('no disks.+pool') and
47f324
+    #               not blivet_output.changed }}"
47f324
+    #     msg: "Unexpected behavior w/ no disks specified"
47f324
 
47f324
     - name: Test for correct handling of LVM volume not defined within a pool.
47f324
       block:
47f324
@@ -142,10 +217,179 @@
47f324
                 size: "{{ volume1_size }}"
47f324
                 mount_point: "{{ mount_location1 }}"
47f324
 
47f324
+        - name: UNREACH
47f324
+          fail:
47f324
+            msg: "this should be unreachable"
47f324
+
47f324
+      rescue:
47f324
+        - name: Check that we failed in the role
47f324
+          assert:
47f324
+            that:
47f324
+              - ansible_failed_task.name != 'UNREACH'
47f324
+            msg: "Role has not failed when it should have"
47f324
+          vars:
47f324
+            # Ugh! needed to expand ansible_failed_task
47f324
+            storage_provider: blivet
47f324
+
47f324
+    # the following does not work properly
47f324
+    # - name: Verify the output
47f324
+    #   assert:
47f324
+    #     that: "{{ blivet_output.failed and
47f324
+    #               blivet_output.msg|regex_search('failed to find pool .+ for volume') and
47f324
+    #               not blivet_output.changed }}"
47f324
+    #     msg: "Unexpected behavior w/ LVM volume defined outside of any pool"
47f324
+
47f324
+    - name: Create a pool
47f324
+      include_role:
47f324
+        name: storage
47f324
+      vars:
47f324
+        storage_pools:
47f324
+          - name: testpool1
47f324
+            type: lvm
47f324
+            disks: "{{ unused_disks }}"
47f324
+            volumes:
47f324
+              - name: testvol1
47f324
+                fs_type: 'ext4'
47f324
+                size: '1g'
47f324
+
47f324
+    - name: Test for correct handling of safe_mode
47f324
+      block:
47f324
+        - name: Try to replace file system in safe mode
47f324
+          include_role:
47f324
+            name: storage
47f324
+          vars:
47f324
+            storage_pools:
47f324
+              - name: testpool1
47f324
+                type: lvm
47f324
+                disks: "{{ unused_disks }}"
47f324
+                volumes:
47f324
+                  - name: testvol1
47f324
+                    fs_type: 'ext3'
47f324
+                    size: '1g'
47f324
+
47f324
+        - name: UNREACH
47f324
+          fail:
47f324
+            msg: "this should be unreachable"
47f324
+
47f324
+      rescue:
47f324
+        - name: Check that we failed in the role
47f324
+          assert:
47f324
+            that:
47f324
+              - ansible_failed_task.name != 'UNREACH'
47f324
+            msg: "Role has not failed when it should have"
47f324
+          vars:
47f324
+            # Ugh! needed to expand ansible_failed_task
47f324
+            storage_provider: blivet
47f324
+
47f324
         - name: Verify the output
47f324
           assert:
47f324
             that: "{{ blivet_output.failed and
47f324
-                      blivet_output.msg|regex_search('failed to find pool .+ for volume') and
47f324
+                      blivet_output.msg|regex_search('cannot remove existing formatting on volume.*in safe mode') and
47f324
                       not blivet_output.changed }}"
47f324
-            msg: "Unexpected behavior w/ LVM volume defined outside of any pool"
47f324
-      ignore_errors: yes
47f324
+            msg: "Unexpected behavior w/ existing data on specified disks"
47f324
+
47f324
+    - name: Test for correct handling of safe_mode with resize
47f324
+      block:
47f324
+        - name: Try to resize in safe mode
47f324
+          include_role:
47f324
+            name: storage
47f324
+          vars:
47f324
+            storage_pools:
47f324
+              - name: testpool1
47f324
+                type: lvm
47f324
+                disks: "{{ unused_disks }}"
47f324
+                volumes:
47f324
+                  - name: testvol1
47f324
+                    fs_type: 'ext4'
47f324
+                    size: '2g'
47f324
+
47f324
+        - name: Verify the output
47f324
+          assert:
47f324
+            that: "{{ not blivet_output.failed and blivet_output.changed }}"
47f324
+            msg: "Unexpected behavior w/ existing data on specified disks"
47f324
+
47f324
+      when: false
47f324
+
47f324
+    - name: Test for correct handling of safe_mode with existing pool
47f324
+      block:
47f324
+        - name: Try to create LVM pool on disks that already belong to an existing pool
47f324
+          include_role:
47f324
+            name: storage
47f324
+          vars:
47f324
+            storage_pools:
47f324
+              - name: foo
47f324
+                disks: "{{ unused_disks }}"
47f324
+                type: lvm
47f324
+
47f324
+        - name: UNREACH
47f324
+          fail:
47f324
+            msg: "this should be unreachable"
47f324
+
47f324
+      rescue:
47f324
+        - name: Check that we failed in the role
47f324
+          assert:
47f324
+            that:
47f324
+              - ansible_failed_task.name != 'UNREACH'
47f324
+            msg: "Role has not failed when it should have"
47f324
+          vars:
47f324
+            # Ugh! needed to expand ansible_failed_task
47f324
+            storage_provider: blivet
47f324
+
47f324
+        - name: Verify the output
47f324
+          assert:
47f324
+            that: "{{ blivet_output.failed and
47f324
+                      blivet_output.msg|regex_search('cannot remove existing formatting and/or devices on disk.*in safe mode') and
47f324
+                      not blivet_output.changed }}"
47f324
+            msg: "Unexpected behavior w/ existing data on specified disks"
47f324
+
47f324
+    - name: Test for correct handling of safe_mode
47f324
+      block:
47f324
+        - name: Try to replace a pool by a file system on disk in safe mode
47f324
+          include_role:
47f324
+            name: storage
47f324
+          vars:
47f324
+            storage_volumes:
47f324
+              - name: test1
47f324
+                type: disk
47f324
+                fs_type: 'ext3'
47f324
+                disks:
47f324
+                  - "{{ unused_disks[0] }}"
47f324
+
47f324
+        - name: UNREACH
47f324
+          fail:
47f324
+            msg: "this should be unreachable"
47f324
+
47f324
+      rescue:
47f324
+        - name: Check that we failed in the role
47f324
+          assert:
47f324
+            that:
47f324
+              - ansible_failed_task.name != 'UNREACH'
47f324
+            msg: "Role has not failed when it should have"
47f324
+          vars:
47f324
+            # Ugh! needed to expand ansible_failed_task
47f324
+            storage_provider: blivet
47f324
+
47f324
+    - name: Verify the output
47f324
+      assert:
47f324
+        that: "blivet_output.failed and
47f324
+               blivet_output.msg|regex_search('cannot remove existing formatting on volume.*in safe mode') and
47f324
+                  not blivet_output.changed"
47f324
+        msg: "Unexpected behavior w/ existing data on specified disks"
47f324
+
47f324
+    - name: Verify the output
47f324
+      assert:
47f324
+        that: "blivet_output.failed and
47f324
+               blivet_output.msg|regex_search('cannot remove existing formatting on volume.*in safe mode') and
47f324
+                  not blivet_output.changed"
47f324
+        msg: "Unexpected behavior w/ existing data on specified disks"
47f324
+
47f324
+    - name: Clean up
47f324
+      include_role:
47f324
+        name: storage
47f324
+      vars:
47f324
+        storage_safe_mode: false
47f324
+        storage_pools:
47f324
+          - name: testpool1
47f324
+            type: lvm
47f324
+            disks: "{{ unused_disks }}"
47f324
+            state: absent
47f324
diff --git a/tests/tests_lvm_multiple_disks_multiple_volumes.yml b/tests/tests_lvm_multiple_disks_multiple_volumes.yml
47f324
index bbc7bb0..ca3968f 100644
47f324
--- a/tests/tests_lvm_multiple_disks_multiple_volumes.yml
47f324
+++ b/tests/tests_lvm_multiple_disks_multiple_volumes.yml
47f324
@@ -15,13 +15,7 @@
47f324
       vars:
47f324
         min_size: "{{ volume_group_size }}"
47f324
         max_return: 2
47f324
-
47f324
-    - block:
47f324
-        - debug:
47f324
-            msg: "There needs to be two unused disks in the system to run this playbook."
47f324
-        - name: End playbook if there isn't two disks available
47f324
-          meta: end_play
47f324
-      when: unused_disks|length < 2
47f324
+        disks_needed: 2
47f324
 
47f324
     - name: Create a logical volume spanning two physical volumes that changes its mount location
47f324
       include_role: