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!"