Skip to content

Commit c95c03a

Browse files
vojtechtrefnyrichm
authored andcommitted
feat: Add support for creating multiple partitions
This adds support for specifying size when creating a partition volume and adding multiple partition volumes in one partition pool. Fixes: #437
1 parent ab90d9a commit c95c03a

4 files changed

Lines changed: 446 additions & 20 deletions

File tree

README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,6 @@ variables:
166166
Valid values for `type`: `lvm`, `disk`, `partition` or `raid`.
167167
The default is determined according to the OS and release (currently `lvm`).
168168

169-
__NOTE__: Support for managing partition volumes is currently very limited,
170-
the role allows creating only a single partition spanning the
171-
entire disk.
172-
173169
- `state`
174170

175171
Valid values are `present` (default behavior) or `absent`. Volumes marked as
@@ -378,6 +374,14 @@ variables:
378374
Size for the thin pool. `thin_pool_size` format is intended to be human-readable,
379375
e.g.: "30g", "50GiB".
380376

377+
- `part_type`
378+
379+
This specifies partition type for newly created partitions on MSDOS/MBR partition table.
380+
Supported values include `primary`, `extended` and `logical`.
381+
If not specified, first three partitions on the disk will be created as primary and fourth
382+
one will be created as logical inside a newly created extended partition.
383+
`part_type` is ignored on GPT partition table.
384+
381385
### `storage_safe_mode`
382386

383387
When true (the default), an error will occur instead of automatically removing existing devices and/or formatting.

library/blivet.py

Lines changed: 98 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,9 @@
198198
vdo_pool_size:
199199
description: vdo_pool_size
200200
type: str
201+
part_type:
202+
description: part_type
203+
type: str
201204
volumes:
202205
description: volumes
203206
type: list
@@ -377,6 +380,7 @@
377380
import os
378381
import traceback
379382
import inspect
383+
import re
380384

381385
BLIVET_PACKAGE = None
382386
LIB_IMP_ERR3 = ""
@@ -392,7 +396,7 @@
392396
from blivet3.errors import RaidError
393397
from blivet3.flags import flags as blivet_flags
394398
from blivet3.formats import fslib, get_format
395-
from blivet3.partitioning import do_partitioning
399+
from blivet3.partitioning import do_partitioning, parted
396400
from blivet3.size import Size
397401
from blivet3.udev import trigger
398402
from blivet3.util import set_up_logging
@@ -409,7 +413,7 @@
409413
from blivet.errors import RaidError
410414
from blivet.flags import flags as blivet_flags
411415
from blivet.formats import fslib, get_format
412-
from blivet.partitioning import do_partitioning
416+
from blivet.partitioning import do_partitioning, parted
413417
from blivet.size import Size
414418
from blivet.udev import trigger
415419
from blivet.util import set_up_logging
@@ -974,20 +978,65 @@ class BlivetPartitionVolume(BlivetVolume):
974978
def _type_check(self):
975979
return self._device.raw_device.type == 'partition'
976980

977-
def _get_device_id(self):
978-
device_id = None
979-
if self._blivet_pool._disks[0].partitioned and len(self._blivet_pool._disks[0].children) == 1:
980-
device_id = self._blivet_pool._disks[0].children[0].name
981+
def _update_from_device(self, param_name):
982+
""" Return True if param_name's value was retrieved from a looked-up device. """
983+
if param_name == 'part_type':
984+
if self._device.raw_device.is_primary:
985+
self._volume['part_type'] = 'primary'
986+
elif self._device.raw_device.is_extended:
987+
self._volume['part_type'] = 'extended'
988+
elif self._device.raw_device.is_logical:
989+
self._volume['part_type'] = 'logical'
990+
elif param_name == 'fs_type':
991+
if self._device.raw_device.is_extended:
992+
self._volume['fs_type'] = 'unformatted'
993+
else:
994+
return super(BlivetPartitionVolume, self)._update_from_device(param_name)
995+
else:
996+
return super(BlivetPartitionVolume, self)._update_from_device(param_name)
981997

982-
return device_id
998+
return True
983999

984-
def _resize(self):
985-
pass
1000+
def _get_part_by_partnum(self, partnum):
1001+
return next((p for p in self._blivet_pool._disks[0].children if (p.parted_partition and p.parted_partition.number == partnum)),
1002+
None)
1003+
1004+
def _get_device_id(self):
1005+
name = self._volume['name']
1006+
if self._blivet_pool._disks and self._blivet_pool._disks[0].partitioned:
1007+
if name in (p.name for p in self._blivet_pool._disks[0].children):
1008+
# partition is specified by its name, e.g. "sda1"
1009+
return name
1010+
elif name and name.isdigit():
1011+
# partition is specified by its partition number, e.g. "1"
1012+
part = self._get_part_by_partnum(int(name))
1013+
if part:
1014+
return os.path.basename(part.path)
1015+
elif name and name[-1].isdigit():
1016+
# this might be something like "test1", not really supported but we used it previously
1017+
# with the "single partition spanning whole disk" approach so lets just keep this
1018+
# supported for backwards compatibility
1019+
match = re.search(r'\d+$', name)
1020+
if match:
1021+
part = self._get_part_by_partnum(int(match.group()))
1022+
if part:
1023+
return os.path.basename(part.path)
1024+
return None
9861025

9871026
def _manage_cache(self):
9881027
if self._volume['cached']:
9891028
raise BlivetAnsibleError("caching is not supported for partition volumes")
9901029

1030+
def _get_part_weight(self):
1031+
# XXX make sure the newly created partitions are in right order. We use the partition
1032+
# number as weight, otherwise blivet would create the partitions in random order breaking
1033+
# the idempotency
1034+
name = self._volume['name']
1035+
match = re.search(r'\d+$', name)
1036+
if match:
1037+
return -int(match.group()) * 100
1038+
return 0
1039+
9911040
def _create(self):
9921041
if self._device:
9931042
return
@@ -1000,13 +1049,41 @@ def _create(self):
10001049
if parent is None:
10011050
raise BlivetAnsibleError("failed to find pool '%s' for volume '%s'" % (self._blivet_pool['name'], self._volume['name']))
10021051

1003-
size = Size("256 MiB")
1004-
maxsize = None
1005-
if isinstance(self._volume['size'], str) and '%' in self._volume['size']:
1006-
maxsize = self._get_size()
1052+
size = self._get_size()
1053+
if size == Size(0):
1054+
# backward compatibility with the old "single partition spanning whole disk"
1055+
# approach -- Size 0 means to use the entire disk
1056+
size = Size("256 MiB")
1057+
grow = True
1058+
else:
1059+
grow = False
1060+
1061+
part_type = self._volume['part_type']
1062+
if part_type == "primary":
1063+
parted_type = parted.PARTITION_NORMAL
1064+
elif part_type == "extended":
1065+
parted_type = parted.PARTITION_EXTENDED
1066+
elif part_type == "logical":
1067+
parted_type = parted.PARTITION_LOGICAL
1068+
else:
1069+
parted_type = None
1070+
if part_type in ("extended", "logical") and parent.format.label_type != "msdos":
1071+
raise BlivetAnsibleError("extended and logical partitions can be created only on MSDOS disk")
1072+
if part_type == "extended":
1073+
if self._volume['encryption']:
1074+
raise BlivetAnsibleError("extended partitions cannot be encrypted")
1075+
if self._volume['mount_point']:
1076+
raise BlivetAnsibleError("extended partitions cannot be mounted")
1077+
if part_type == "extended":
1078+
fmt = None
1079+
self._volume['fs_type'] = 'unformatted'
1080+
else:
1081+
fmt = self._get_format()
10071082

10081083
try:
1009-
device = self._blivet.new_partition(parents=[parent], size=size, maxsize=maxsize, grow=True, fmt=self._get_format())
1084+
device = self._blivet.new_partition(parents=[parent], size=size, grow=grow, fmt=fmt,
1085+
weight=self._get_part_weight(),
1086+
part_type=parted_type)
10101087
except Exception:
10111088
raise BlivetAnsibleError("failed set up volume '%s'" % self._volume['name'])
10121089

@@ -1708,8 +1785,12 @@ def _type_check(self):
17081785
return self._device.partitionable
17091786

17101787
def _look_up_device(self):
1711-
self._look_up_disks()
1712-
self._device = self._disks[0]
1788+
device = self._blivet.devicetree.resolve_device(self._pool['name'])
1789+
if device is not None:
1790+
self._device = device
1791+
else:
1792+
self._look_up_disks()
1793+
self._device = self._disks[0]
17131794

17141795
def _create(self):
17151796
if self._device.format.type != "disklabel" or \
@@ -2361,6 +2442,7 @@ def run_module():
23612442
cache_size=dict(type='str'),
23622443
compression=dict(type='bool'),
23632444
deduplication=dict(type='bool'),
2445+
part_type=dict(type='str'),
23642446
raid_disks=dict(type='list', elements='str', default=list()),
23652447
raid_stripe_size=dict(type='str'),
23662448
thin_pool_name=dict(type='str'),
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
---
2+
- name: Test creating multiple partitions on MSDOS/MBR
3+
hosts: all
4+
become: true
5+
vars:
6+
storage_safe_mode: false
7+
volume1_size: '2g'
8+
volume2_size: '3g'
9+
volume3_size: '1g'
10+
storage_disklabel_type: 'msdos'
11+
12+
tasks:
13+
- name: Run the role
14+
include_role:
15+
name: linux-system-roles.storage
16+
17+
- name: Mark tasks to be skipped
18+
set_fact:
19+
storage_skip_checks:
20+
- blivet_available
21+
- "{{ (lookup('env',
22+
'SYSTEM_ROLES_REMOVE_CLOUD_INIT') in ['', 'false']) |
23+
ternary('packages_installed', '') }}"
24+
- service_facts
25+
26+
- name: Get unused disks
27+
include_tasks: get_unused_disk.yml
28+
vars:
29+
max_return: 1
30+
31+
- name: Try to create mounted extended partition (expect failure)
32+
include_tasks: verify-role-failed.yml
33+
vars:
34+
__storage_failed_exception: extended partitions cannot be mounted
35+
__storage_failed_params:
36+
storage_safe_mode: false
37+
storage_pools:
38+
- name: "{{ unused_disks[0] }}"
39+
type: partition
40+
disks: "{{ unused_disks }}"
41+
volumes:
42+
- name: 1
43+
type: partition
44+
fs_type: ext4
45+
part_type: extended
46+
size: "{{ volume1_size }}"
47+
mount_point: /opt/test
48+
49+
- name: Create three partitions
50+
include_role:
51+
name: linux-system-roles.storage
52+
vars:
53+
storage_pools:
54+
- name: "{{ unused_disks[0] }}"
55+
type: partition
56+
disks: "{{ unused_disks }}"
57+
volumes:
58+
- name: 1
59+
type: partition
60+
fs_type: ext4
61+
size: "{{ volume1_size }}"
62+
- name: 2
63+
type: partition
64+
part_type: extended
65+
size: "{{ volume2_size }}"
66+
- name: 5
67+
type: partition
68+
fs_type: ext4
69+
part_type: logical
70+
size: "{{ volume3_size }}"
71+
72+
- name: Verify role results
73+
include_tasks: verify-role-results.yml
74+
75+
- name: Repeat the previous invocation to verify idempotence
76+
include_role:
77+
name: linux-system-roles.storage
78+
vars:
79+
storage_pools:
80+
- name: "{{ unused_disks[0] }}"
81+
type: partition
82+
disks: "{{ unused_disks }}"
83+
volumes:
84+
- name: 1
85+
type: partition
86+
fs_type: ext4
87+
size: "{{ volume1_size }}"
88+
- name: 2
89+
type: partition
90+
part_type: extended
91+
size: "{{ volume2_size }}"
92+
- name: 5
93+
type: partition
94+
fs_type: ext4
95+
part_type: logical
96+
size: "{{ volume3_size }}"
97+
98+
- name: Verify role results - 2
99+
include_tasks: verify-role-results.yml
100+
101+
- name: Remove all partitions
102+
include_role:
103+
name: linux-system-roles.storage
104+
vars:
105+
storage_pools:
106+
- name: "{{ unused_disks[0] }}"
107+
type: partition
108+
disks: "{{ unused_disks }}"
109+
state: absent

0 commit comments

Comments
 (0)