Blob Blame History Raw
diff --git a/library/blivet.py b/library/blivet.py
index cb48e71..e1903f3 100644
--- a/library/blivet.py
+++ b/library/blivet.py
@@ -167,11 +167,16 @@ class BlivetBase(object):
         raise NotImplementedError()
 
     def _manage_one_encryption(self, device):
+        global safe_mode
         ret = device
         # Make sure to handle adjusting both existing stacks and future stacks.
         if device == device.raw_device and self._spec_dict['encryption']:
             # add luks
             luks_name = "luks-%s" % device._name
+            if safe_mode and (device.original_format.type is not None or
+                              device.original_format.name != get_format(None).name):
+                raise BlivetAnsibleError("cannot remove existing formatting on device '%s' in safe mode due to adding encryption" %
+                                         device._name)
             if not device.format.exists:
                 fmt = device.format
             else:
@@ -196,6 +201,10 @@ class BlivetBase(object):
             ret = luks_device
         elif device != device.raw_device and not self._spec_dict['encryption']:
             # remove luks
+            if safe_mode and (device.original_format.type is not None or
+                              device.original_format.name != get_format(None).name):
+                raise BlivetAnsibleError("cannot remove existing formatting on device '%s' in safe mode due to encryption removal" %
+                                         device._name)
             if not device.format.exists:
                 fmt = device.format
             else:
@@ -823,17 +832,21 @@ class BlivetPool(BlivetBase):
 
     def manage(self):
         """ Schedule actions to configure this pool according to the yaml input. """
+        global safe_mode
         # look up the device
         self._look_up_disks()
         self._look_up_device()
 
         # schedule destroy if appropriate, including member type change
-        if not self.ultimately_present or self._member_management_is_destructive():
-            if not self.ultimately_present:
-                self._manage_volumes()
+        if not self.ultimately_present:
+            self._manage_volumes()
             self._destroy()
-            if not self.ultimately_present:
-                return
+            return
+        elif self._member_management_is_destructive():
+            if safe_mode:
+                raise BlivetAnsibleError("cannot remove and recreate existing pool '%s' in safe mode" % self._pool['name'])
+            else:
+                self._destroy()
 
         # schedule create if appropriate
         self._create()
diff --git a/tests/create-test-file.yml b/tests/create-test-file.yml
new file mode 100644
index 0000000..d1091e2
--- /dev/null
+++ b/tests/create-test-file.yml
@@ -0,0 +1,13 @@
+# Create a file to be checked that it still exists and no data loss has occured.
+# To use:
+# - set testfile to a path under the mountpoint being tested
+# - include this file (create-test-file.yml) before executing the
+#   operation to be tested
+# - execute the operation that could potentially result in a loss of
+#   data in the filesystem where testfile is located
+# - include verify-data-preservation.yml
+
+- name: create a file
+  file:
+    path: "{{ testfile }}"
+    state: touch
diff --git a/tests/tests_luks.yml b/tests/tests_luks.yml
index f93efe5..f733714 100644
--- a/tests/tests_luks.yml
+++ b/tests/tests_luks.yml
@@ -2,8 +2,8 @@
 - hosts: all
   become: true
   vars:
-    storage_safe_mode: false
     mount_location: '/opt/test1'
+    testfile: "{{ mount_location }}/quux"
     volume_size: '5g'
 
   tasks:
@@ -64,10 +64,47 @@
 
     - include_tasks: verify-role-results.yml
 
+    - import_tasks: create-test-file.yml
+
+    - name: Test for correct handling of safe_mode
+      block:
+        - name: Remove the encryption layer
+          include_role:
+            name: storage
+          vars:
+            storage_volumes:
+              - name: foo
+                type: disk
+                disks: "{{ unused_disks }}"
+                mount_point: "{{ mount_location }}"
+                encryption: false
+                encryption_password: 'yabbadabbadoo'
+        - name: unreachable task
+          fail:
+            msg: UNREACH
+      rescue:
+        - name: Check that we failed in the role
+          assert:
+            that:
+              - ansible_failed_result.msg != 'UNREACH'
+            msg: "Role has not failed when it should have"
+
+        - name: Verify the output of the safe_mode test
+          assert:
+            that: "blivet_output.failed and
+                   blivet_output.msg
+                   |regex_search('cannot remove existing
+                   formatting.*in safe mode due to encryption removal')
+                   and not blivet_output.changed"
+            msg: "Unexpected behavior w/ existing filesystem in safe mode"
+
+    - import_tasks: verify-data-preservation.yml
+
     - name: Remove the encryption layer
       include_role:
         name: storage
       vars:
+        storage_safe_mode: false
         storage_volumes:
           - name: foo
             type: disk
@@ -78,10 +115,47 @@
 
     - include_tasks: verify-role-results.yml
 
+    - import_tasks: create-test-file.yml
+
+    - name: Test for correct handling of safe_mode
+      block:
+        - name: Add encryption to the volume
+          include_role:
+            name: storage
+          vars:
+            storage_volumes:
+              - name: foo
+                type: disk
+                disks: "{{ unused_disks }}"
+                mount_point: "{{ mount_location }}"
+                encryption: true
+                encryption_password: 'yabbadabbadoo'
+        - name: unreachable task
+          fail:
+            msg: UNREACH
+      rescue:
+        - name: Check that we failed in the role
+          assert:
+            that:
+              - ansible_failed_result.msg != 'UNREACH'
+            msg: "Role has not failed when it should have"
+
+        - name: Verify the output of the safe_mode test
+          assert:
+            that: "blivet_output.failed and
+                   blivet_output.msg
+                   |regex_search('cannot remove existing
+                   formatting.*in safe mode due to adding encryption')
+                   and not blivet_output.changed"
+            msg: "Unexpected behavior w/ existing filesystem in safe mode"
+
+    - import_tasks: verify-data-preservation.yml
+
     - name: Add encryption to the volume
       include_role:
         name: storage
       vars:
+        storage_safe_mode: false
         storage_volumes:
           - name: foo
             type: disk
@@ -102,6 +176,7 @@
           include_role:
             name: storage
           vars:
+            storage_safe_mode: false
             storage_pools:
               - name: foo
                 type: partition
@@ -135,6 +210,7 @@
       include_role:
         name: storage
       vars:
+        storage_safe_mode: false
         storage_pools:
           - name: foo
             type: partition
@@ -149,10 +225,51 @@
 
     - include_tasks: verify-role-results.yml
 
+    - import_tasks: create-test-file.yml
+
+    - name: Test for correct handling of safe_mode
+      block:
+        - name: Remove the encryption layer
+          include_role:
+            name: storage
+          vars:
+            storage_pools:
+              - name: foo
+                type: partition
+                disks: "{{ unused_disks }}"
+                volumes:
+                  - name: test1
+                    type: partition
+                    mount_point: "{{ mount_location }}"
+                    size: 4g
+                    encryption: false
+                    encryption_password: 'yabbadabbadoo'
+        - name: unreachable task
+          fail:
+            msg: UNREACH
+      rescue:
+        - name: Check that we failed in the role
+          assert:
+            that:
+              - ansible_failed_result.msg != 'UNREACH'
+            msg: "Role has not failed when it should have"
+
+        - name: Verify the output of the safe_mode test
+          assert:
+            that: "blivet_output.failed and
+                   blivet_output.msg
+                   |regex_search('cannot remove existing
+                   formatting.*in safe mode due to encryption removal')
+                   and not blivet_output.changed"
+            msg: "Unexpected behavior w/ existing filesystem in safe mode"
+
+    - import_tasks: verify-data-preservation.yml
+
     - name: Remove the encryption layer
       include_role:
         name: storage
       vars:
+        storage_safe_mode: false
         storage_pools:
           - name: foo
             type: partition
@@ -167,6 +284,48 @@
 
     - include_tasks: verify-role-results.yml
 
+    - import_tasks: create-test-file.yml
+
+    - name: Test for correct handling of safe_mode
+      block:
+        - name: Add encryption to the volume
+          include_role:
+            name: storage
+          vars:
+            storage_pools:
+              - name: foo
+                type: partition
+                disks: "{{ unused_disks }}"
+                volumes:
+                  - name: test1
+                    type: partition
+                    mount_point: "{{ mount_location }}"
+                    size: 4g
+                    encryption: true
+                    encryption_password: 'yabbadabbadoo'
+
+        - name: unreachable task
+          fail:
+            msg: UNREACH
+
+      rescue:
+        - name: Check that we failed in the role
+          assert:
+            that:
+              - ansible_failed_result.msg != 'UNREACH'
+            msg: "Role has not failed when it should have"
+
+        - name: Verify the output of the safe_mode test
+          assert:
+            that: "blivet_output.failed and
+                   blivet_output.msg
+                   |regex_search('cannot remove existing
+                   formatting.*in safe mode due to adding encryption')
+                   and not blivet_output.changed"
+            msg: "Unexpected behavior w/ existing volume in safe mode"
+
+    - import_tasks: verify-data-preservation.yml
+
     - name: Test key file handling
       block:
         - name: Create a key file
@@ -186,6 +345,7 @@
           include_role:
             name: storage
           vars:
+            storage_safe_mode: false
             storage_pools:
               - name: foo
                 type: partition
@@ -216,6 +376,7 @@
           include_role:
             name: storage
           vars:
+            storage_safe_mode: false
             storage_pools:
               - name: foo
                 type: lvm
@@ -248,6 +409,7 @@
       include_role:
         name: storage
       vars:
+        storage_safe_mode: false
         storage_pools:
           - name: foo
             type: lvm
@@ -264,10 +426,52 @@
 
     - include_tasks: verify-role-results.yml
 
+    - import_tasks: create-test-file.yml
+
+    - name: Test for correct handling of safe_mode
+      block:
+        - name: Remove the encryption layer
+          include_role:
+            name: storage
+          vars:
+            storage_pools:
+              - name: foo
+                type: lvm
+                disks: "{{ unused_disks }}"
+                volumes:
+                  - name: test1
+                    mount_point: "{{ mount_location }}"
+                    size: 4g
+                    encryption: false
+                    encryption_password: 'yabbadabbadoo'
+
+        - name: unreachable task
+          fail:
+            msg: UNREACH
+
+      rescue:
+        - name: Check that we failed in the role
+          assert:
+            that:
+              - ansible_failed_result.msg != 'UNREACH'
+            msg: "Role has not failed when it should have"
+
+        - name: Verify the output of the safe_mode test
+          assert:
+            that: "blivet_output.failed and
+                   blivet_output.msg
+                   |regex_search('cannot remove existing
+                   formatting.*in safe mode due to encryption removal')
+                   and not blivet_output.changed"
+            msg: "Unexpected behavior w/ existing volume in safe mode"
+
+    - import_tasks: verify-data-preservation.yml
+
     - name: Remove the encryption layer
       include_role:
         name: storage
       vars:
+        storage_safe_mode: false
         storage_pools:
           - name: foo
             type: lvm
@@ -281,10 +485,52 @@
 
     - include_tasks: verify-role-results.yml
 
+    - import_tasks: create-test-file.yml
+
+    - name: Test for correct handling of safe_mode
+      block:
+        - name: Add encryption to the volume
+          include_role:
+            name: storage
+          vars:
+            storage_pools:
+              - name: foo
+                type: lvm
+                disks: "{{ unused_disks }}"
+                volumes:
+                  - name: test1
+                    mount_point: "{{ mount_location }}"
+                    size: 4g
+                    encryption: true
+                    encryption_password: 'yabbadabbadoo'
+
+        - name: unreachable task
+          fail:
+            msg: UNREACH
+
+      rescue:
+        - name: Check that we failed in the role
+          assert:
+            that:
+              - ansible_failed_result.msg != 'UNREACH'
+            msg: "Role has not failed when it should have"
+
+        - name: Verify the output of the safe_mode test
+          assert:
+            that: "blivet_output.failed and
+                   blivet_output.msg
+                   |regex_search('cannot remove existing
+                   formatting.*in safe mode due to adding encryption')
+                   and not blivet_output.changed"
+            msg: "Unexpected behavior w/ existing volume in safe mode"
+
+    - import_tasks: verify-data-preservation.yml
+
     - name: Add encryption to the volume
       include_role:
         name: storage
       vars:
+        storage_safe_mode: false
         storage_pools:
           - name: foo
             type: lvm
diff --git a/tests/tests_luks_pool.yml b/tests/tests_luks_pool.yml
index b20b806..f44916f 100644
--- a/tests/tests_luks_pool.yml
+++ b/tests/tests_luks_pool.yml
@@ -2,9 +2,10 @@
 - hosts: all
   become: true
   vars:
-    storage_safe_mode: false
     mount_location: '/opt/test1'
     mount_location_2: '/opt/test2'
+    testfile: "{{ mount_location }}/quux"
+    testfile_location_2: "{{ mount_location_2 }}/quux"
     volume_size: '5g'
 
   tasks:
@@ -92,10 +93,50 @@
             state: absent
           changed_when: false
 
+    - import_tasks: create-test-file.yml
+
+    - name: Test for correct handling of safe_mode
+      block:
+        - name: Remove the encryption layer
+          include_role:
+            name: storage
+          vars:
+            storage_pools:
+              - name: foo
+                type: lvm
+                disks: "{{ unused_disks }}"
+                encryption: false
+                encryption_password: 'yabbadabbadoo'
+                volumes:
+                  - name: test1
+                    mount_point: "{{ mount_location }}"
+                    size: 4g
+        - name: unreachable task
+          fail:
+            msg: UNREACH
+      rescue:
+        - name: Check that we failed in the role
+          assert:
+            that:
+              - ansible_failed_result.msg != 'UNREACH'
+            msg: "Role has not failed when it should have"
+
+        - name: Verify the output of the safe_mode test
+          assert:
+            that: "blivet_output.failed and
+                   blivet_output.msg
+                   |regex_search('cannot remove and recreate existing
+                   pool.*in safe mode')
+                   and not blivet_output.changed"
+            msg: "Unexpected behavior w/ existing pool in safe mode"
+
+    - import_tasks: verify-data-preservation.yml
+
     - name: Remove the encryption layer
       include_role:
         name: storage
       vars:
+        storage_safe_mode: false
         storage_pools:
           - name: foo
             type: lvm
@@ -109,10 +150,53 @@
 
     - include_tasks: verify-role-results.yml
 
-    - name: Add encryption to the volume
+    - import_tasks: create-test-file.yml
+
+    - name: Test for correct handling of safe_mode
+      block:
+        - name: Add encryption to the pool
+          include_role:
+            name: storage
+          vars:
+            storage_pools:
+              - name: foo
+                type: lvm
+                disks: "{{ unused_disks }}"
+                encryption: true
+                encryption_password: 'yabbadabbadoo'
+                encryption_luks_version: luks1
+                encryption_key_size: 512
+                encryption_cipher: 'serpent-xts-plain64'
+                volumes:
+                  - name: test1
+                    mount_point: "{{ mount_location }}"
+                    size: 4g
+        - name: unreachable task
+          fail:
+            msg: UNREACH
+      rescue:
+        - name: Check that we failed in the role
+          assert:
+            that:
+              - ansible_failed_result.msg != 'UNREACH'
+            msg: "Role has not failed when it should have"
+
+        - name: Verify the output of the safe_mode test
+          assert:
+            that: "blivet_output.failed and
+                   blivet_output.msg
+                   |regex_search('cannot remove and recreate existing
+                   pool.*in safe mode')
+                   and not blivet_output.changed"
+            msg: "Unexpected behavior w/ existing pool in safe mode"
+
+    - import_tasks: verify-data-preservation.yml
+
+    - name: Add encryption to the pool
       include_role:
         name: storage
       vars:
+        storage_safe_mode: false
         storage_pools:
           - name: foo
             type: lvm
@@ -129,6 +213,8 @@
 
     - include_tasks: verify-role-results.yml
 
+    - import_tasks: create-test-file.yml
+
     - name: Change the mountpoint, leaving encryption in place
       include_role:
         name: storage
@@ -144,6 +230,10 @@
                 mount_point: "{{ mount_location_2 }}"
                 size: 4g
 
+    - import_tasks: verify-data-preservation.yml
+      vars:
+        testfile: "{{ testfile_location_2 }}"
+
     - include_tasks: verify-role-results.yml
 
     - name: Clean up
diff --git a/tests/verify-data-preservation.yml b/tests/verify-data-preservation.yml
new file mode 100644
index 0000000..eed790f
--- /dev/null
+++ b/tests/verify-data-preservation.yml
@@ -0,0 +1,19 @@
+# Verify that a file still exists and no data loss has occured.
+# To use:
+# - set testfile to a path under the mountpoint being tested
+# - include create-test-file.yml before executing the operation to be
+#   tested
+# - execute the operation that could potentially result in a loss of
+#   data in the filesystem where testfile is located
+# - include this file (verify-data-preservation.yml)
+
+- name: stat the file
+  stat:
+    path: "{{ testfile }}"
+  register: stat_r
+
+- name: assert file presence
+  assert:
+    that:
+      stat_r.stat.isreg is defined and stat_r.stat.isreg
+    msg: "data lost!"