Skip to content

Commit 822a07d

Browse files
authored
Merge pull request #43 from dwlehman/non-destructive
Add flag to prevent implicit removal of existing device/fs.
2 parents bfa4f29 + f9ce0dd commit 822a07d

12 files changed

+585
-65
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ The `mount_point` specifies the directory on which the file system will be mount
7373
##### `mount_options`
7474
The `mount_options` specifies custom mount options as a string, e.g.: 'ro'.
7575

76+
#### `storage_safe_mode`
77+
When true (the default), an error will occur instead of automatically removing existing devices and/or formatting.
78+
7679

7780
Example Playbook
7881
----------------

defaults/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
storage_provider: "blivet"
44
storage_use_partitions: null
55
storage_disklabel_type: null # leave unset to allow the role to select an appropriate label type
6+
storage_safe_mode: true # fail instead of implicitly/automatically removing devices or formatting
67

78
storage_pool_defaults:
89
state: "present"

library/blivet.py

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
disklabel_type:
3232
description:
3333
- disklabel type string (eg: 'gpt') to use, overriding the built-in logic in blivet
34+
safe_mode:
35+
description:
36+
- boolean indicating that we should fail rather than implicitly/automatically
37+
removing devices or formatting
3438
3539
author:
3640
- David Lehman (dlehman@redhat.com)
@@ -116,6 +120,8 @@
116120

117121
use_partitions = None # create partitions on pool backing device disks?
118122
disklabel_type = None # user-specified disklabel type
123+
safe_mode = None # do not remove any existing devices or formatting
124+
packages_only = None # only set things up enough to get a list of required packages
119125

120126

121127
class BlivetAnsibleError(Exception):
@@ -163,7 +169,6 @@ def _get_format(self):
163169
label=self._volume['fs_label'],
164170
options=self._volume['fs_create_options'])
165171
if not fmt.supported or not fmt.formattable:
166-
# FAIL: fs type tools are not available
167172
raise BlivetAnsibleError("required tools for file system '%s' are missing" % self._volume['fs_type'])
168173

169174
return fmt
@@ -189,32 +194,35 @@ def _resize(self):
189194
try:
190195
size = Size(self._volume['size'])
191196
except Exception:
192-
# FAIL: invalid size specification
193197
raise BlivetAnsibleError("invalid size specification for volume '%s': '%s'" % (self._volume['name'], self._volume['size']))
194198

195199
if size and self._device.resizable and self._device.size != size:
196200
if self._device.format.resizable:
197201
self._device.format.update_size_info()
198202

199203
if not self._device.min_size <= size <= self._device.max_size:
200-
# FAIL: resize to specified size not possible
201204
raise BlivetAnsibleError("volume '%s' cannot be resized to '%s'" % (self._volume['name'], size))
202205

203206
try:
204207
self._blivet.resize_device(self._device, size)
205208
except ValueError as e:
206-
# FAIL: resize not possible
207209
raise BlivetAnsibleError("volume '%s' cannot be resized from %s to %s: %s" % (self._device.name,
208210
self._device.size,
209211
size, str(e)))
210212

211213
def _reformat(self):
212214
""" Schedule actions as needed to ensure the volume is formatted as specified. """
215+
global packages_only
216+
213217
fmt = self._get_format()
214218
if self._device.format.type == fmt.type:
215219
return
216220

217-
if self._device.format.status:
221+
if safe_mode and (self._device.format.type is not None or self._device.format.name != get_format(None).name) and \
222+
not packages_only:
223+
raise BlivetAnsibleError("cannot remove existing formatting on volume '%s' in safe mode" % self._volume['name'])
224+
225+
if self._device.format.status and not packages_only:
218226
self._device.format.teardown()
219227
self._blivet.format_device(self._device, fmt)
220228

@@ -255,6 +263,17 @@ def _get_device_id(self):
255263
def _type_check(self):
256264
return self._device.is_disk
257265

266+
def _look_up_device(self):
267+
super(BlivetDiskVolume, self)._look_up_device()
268+
if not self._get_device_id():
269+
raise BlivetAnsibleError("no disks specified for volume '%s'" % self._volume['name'])
270+
elif not isinstance(self._volume['disks'], list):
271+
raise BlivetAnsibleError("volume disks must be specified as a list")
272+
273+
if self._device is None:
274+
raise BlivetAnsibleError("unable to resolve disk specified for volume '%s' (%s)" % (self._volume['name'], self._volume['disks']))
275+
276+
258277

259278
class BlivetPartitionVolume(BlivetVolume):
260279
def _type_check(self):
@@ -273,21 +292,18 @@ def _create(self):
273292
parent = self._blivet.devicetree.resolve_device(self._volume['pool'])
274293

275294
if parent is None:
276-
# FAIL: failed to find pool
277295
raise BlivetAnsibleError("failed to find pool '%s' for volume '%s'" % (self._blivet_pool['name'], self._volume['name']))
278296

279297
size = Size("256 MiB")
280298
try:
281299
device = self._blivet.new_partition(parents=[parent], size=size, grow=True, fmt=self._get_format())
282300
except Exception:
283-
# FAIL: failed to instantiate volume device
284301
raise BlivetAnsibleError("failed set up volume '%s'" % self._volume['name'])
285302

286303
self._blivet.create_device(device)
287304
try:
288305
do_partitioning(self._blivet)
289306
except Exception:
290-
# FAIL: partition allocation failed: not enough space?
291307
raise BlivetAnsibleError("partition allocation failed for volume '%s'" % self._volume['name'])
292308

293309
self._device = device
@@ -303,18 +319,15 @@ def _create(self):
303319

304320
parent = self._blivet_pool._device
305321
if parent is None:
306-
# FAIL: failed to find pool
307322
raise BlivetAnsibleError("failed to find pool '%s' for volume '%s'" % (self._blivet_pool['name'], self._volume['name']))
308323

309324
try:
310325
size = Size(self._volume['size'])
311326
except Exception:
312-
# FAIL: invalid size specification
313327
raise BlivetAnsibleError("invalid size '%s' specified for volume '%s'" % (self._volume['size'], self._volume['name']))
314328

315329
fmt = self._get_format()
316330
if size > parent.free_space:
317-
# FAIL: volume size greater than pool free space
318331
raise BlivetAnsibleError("specified size for volume '%s' exceeds available space in pool '%s' (%s)" % (size,
319332
parent.name,
320333
parent.free_space))
@@ -323,7 +336,6 @@ def _create(self):
323336
device = self._blivet.new_lv(name=self._volume['name'],
324337
parents=[parent], size=size, fmt=fmt)
325338
except Exception:
326-
# FAIL: failed to create volume
327339
raise BlivetAnsibleError("failed to set up volume '%s'" % self._volume['name'])
328340

329341
self._blivet.create_device(device)
@@ -391,8 +403,7 @@ def _type_check(self): # pylint: disable=no-self-use
391403
def _look_up_disks(self):
392404
""" Look up the pool's disks in blivet's device tree. """
393405
if not self._pool['disks']:
394-
# FAIL: no disks specified for pool
395-
raise BlivetAnsibleError("no disks specified for pool '%s'" % self._pool['name']) # sure about this one?
406+
raise BlivetAnsibleError("no disks specified for pool '%s'" % self._pool['name'])
396407
elif not isinstance(self._pool['disks'], list):
397408
raise BlivetAnsibleError("pool disks must be specified as a list")
398409

@@ -403,7 +414,6 @@ def _look_up_disks(self):
403414
disks.append(device)
404415

405416
if self._pool['disks'] and not disks:
406-
# FAIL: failed to find any disks
407417
raise BlivetAnsibleError("unable to resolve any disks specified for pool '%s' (%s)" % (self._pool['name'], self._pool['disks']))
408418

409419
self._disks = disks
@@ -428,8 +438,11 @@ def _create_members(self):
428438
""" Schedule actions as needed to ensure pool member devices exist. """
429439
members = list()
430440
for disk in self._disks:
431-
if not disk.isleaf:
432-
self._blivet.devicetree.recursive_remove(disk)
441+
if not disk.isleaf or disk.format.type is not None:
442+
if safe_mode and not packages_only:
443+
raise BlivetAnsibleError("cannot remove existing formatting and/or devices on disk '%s' (pool '%s') in safe mode" % (disk.name, self._pool['name']))
444+
else:
445+
self._blivet.devicetree.recursive_remove(disk)
433446

434447
if use_partitions:
435448
label = get_format("disklabel", device=disk.path)
@@ -446,7 +459,6 @@ def _create_members(self):
446459
try:
447460
do_partitioning(self._blivet)
448461
except Exception:
449-
# FAIL: problem allocating partitions for pool backing devices
450462
raise BlivetAnsibleError("failed to allocation partitions for pool '%s'" % self._pool['name'])
451463

452464
return members
@@ -490,7 +502,11 @@ def _look_up_device(self):
490502
def _create(self):
491503
if self._device.format.type != "disklabel" or \
492504
self._device.format.label_type != disklabel_type:
493-
self._blivet.devicetree.recursive_remove(self._device, remove_device=False)
505+
if safe_mode and not packages_only:
506+
raise BlivetAnsibleError("cannot remove existing formatting and/or devices on disk '%s' "
507+
"(pool '%s') in safe mode" % (self._device.name, self._pool['name']))
508+
else:
509+
self._blivet.devicetree.recursive_remove(self._device, remove_device=False)
494510

495511
label = get_format("disklabel", device=self._device.path, label_type=disklabel_type)
496512
self._blivet.format_device(self._device, label)
@@ -503,7 +519,6 @@ def _type_check(self):
503519
def _get_format(self):
504520
fmt = get_format("lvmpv")
505521
if not fmt.supported or not fmt.formattable:
506-
# FAIL: lvm tools are not available
507522
raise BlivetAnsibleError("required tools for managing LVM are missing")
508523

509524
return fmt
@@ -516,15 +531,14 @@ def _create(self):
516531
try:
517532
pool_device = self._blivet.new_vg(name=self._pool['name'], parents=members)
518533
except Exception:
519-
# FAIL: failed to instantiate pool device
520534
raise BlivetAnsibleError("failed to set up pool '%s'" % self._pool['name'])
521535

522536
self._blivet.create_device(pool_device)
523537
self._device = pool_device
524538

525539

526540
_BLIVET_POOL_TYPES = {
527-
"disk": BlivetPartitionPool,
541+
"partition": BlivetPartitionPool,
528542
"lvm": BlivetLVMPool
529543
}
530544

@@ -660,6 +674,7 @@ def run_module():
660674
volumes=dict(type='list'),
661675
packages_only=dict(type='bool', required=False, default=False),
662676
disklabel_type=dict(type='str', required=False, default=None),
677+
safe_mode=dict(type='bool', required=False, default=True),
663678
use_partitions=dict(type='bool', required=False, default=True))
664679

665680
# seed the result dict in the object
@@ -692,6 +707,12 @@ def run_module():
692707
global use_partitions
693708
use_partitions = module.params['use_partitions']
694709

710+
global safe_mode
711+
safe_mode = module.params['safe_mode']
712+
713+
global packages_only
714+
packages_only = module.params['packages_only']
715+
695716
b = Blivet()
696717
b.reset()
697718
fstab = FSTab(b)

tasks/main-blivet.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
_storage_vols_no_defaults: "{{ _storage_vols_no_defaults|default([]) }} + [{{ item.1 }}]"
3939
_storage_vol_defaults: "{{ _storage_vol_defaults|default([]) }} + [{{ storage_volume_defaults }}]"
4040
_storage_vol_pools: "{{ _storage_vol_pools|default([]) }} + ['{{ item.0.name }}']"
41-
loop: "{{ _storage_pools|subelements('volumes') }}"
41+
loop: "{{ _storage_pools|subelements('volumes', skip_missing=true) }}"
4242
when: storage_pools is defined
4343

4444
- name: Apply defaults to pools and volumes [3/6]
@@ -105,6 +105,7 @@
105105
volumes: "{{ _storage_volumes }}"
106106
use_partitions: "{{ storage_use_partitions }}"
107107
disklabel_type: "{{ storage_disklabel_type }}"
108+
safe_mode: "{{ storage_safe_mode }}"
108109
register: blivet_output
109110

110111
- debug:

tests/tests_change_disk_fs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
- hosts: all
33
become: true
44
vars:
5+
storage_safe_mode: false
56
mount_location: '/opt/test'
67
volume_size: '5g'
78
fs_type_after: "{{ 'ext3' if (ansible_distribution == 'RedHat' and ansible_distribution_major_version == '6') else 'ext4' }}"

tests/tests_change_fs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
- hosts: all
33
become: true
44
vars:
5+
storage_safe_mode: false
56
mount_location: '/opt/test1'
67
volume_size: '5g'
78
fs_after: "{{ (ansible_distribution == 'RedHat' and ansible_distribution_major_version == '6') | ternary('ext4', 'xfs') }}"

tests/tests_change_fs_use_partitions.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
- hosts: all
33
become: true
44
vars:
5+
storage_safe_mode: false
56
storage_use_partitions: true
67
mount_location: '/opt/test1'
78
volume_size: '5g'

tests/tests_create_disk_then_remove.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
- hosts: all
33
become: true
44
vars:
5+
storage_safe_mode: false
56
mount_location: '/opt/test1'
67

78
tasks:

tests/tests_create_lvm_pool_then_remove.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
- hosts: all
33
become: true
44
vars:
5+
storage_safe_mode: false
56
mount_location1: '/opt/test1'
67
mount_location2: '/opt/test2'
78
volume_group_size: '10g'

tests/tests_create_partition_volume_then_remove.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
- hosts: all
33
become: true
44
vars:
5+
storage_safe_mode: false
56
mount_location: '/opt/test1'
67

78
tasks:
@@ -18,7 +19,7 @@
1819
vars:
1920
storage_pools:
2021
- name: "{{ unused_disks[0] }}"
21-
type: disk
22+
type: partition
2223
disks: "{{ unused_disks }}"
2324
volumes:
2425
- name: test1
@@ -33,7 +34,7 @@
3334
vars:
3435
storage_pools:
3536
- name: "{{ unused_disks[0] }}"
36-
type: disk
37+
type: partition
3738
disks: "{{ unused_disks }}"
3839
volumes:
3940
- name: test1
@@ -48,7 +49,7 @@
4849
vars:
4950
storage_pools:
5051
- name: "{{ unused_disks[0] }}"
51-
type: disk
52+
type: partition
5253
disks: "{{ unused_disks }}"
5354
state: absent
5455
volumes:
@@ -65,7 +66,7 @@
6566
vars:
6667
storage_pools:
6768
- name: "{{ unused_disks[0] }}"
68-
type: disk
69+
type: partition
6970
disks: "{{ unused_disks }}"
7071
state: absent
7172
volumes:

0 commit comments

Comments
 (0)