items/zfs_pool: rewrite item to support all kinds of zfs pools
All checks were successful
kunsi/bundlewrap/pipeline/head This commit looks good

This commit is contained in:
Franzi 2021-07-17 18:09:35 +02:00
parent 9111d49bf4
commit cee2a41771
Signed by: kunsi
GPG key ID: 12E3D2136B818350
10 changed files with 134 additions and 117 deletions

View file

@ -6,43 +6,6 @@ from bundlewrap.items import Item
from bundlewrap.utils.text import mark_for_translation as _
def create_mirrors(node, path, mirrors):
cmd = ""
for devices in mirrors:
actual_targets = []
for device in devices:
actual_targets.append(quote(prepare_blockdevice(node, device)))
cmd += "mirror {} ".format(" ".join(actual_targets))
node.run("zpool create {} {}".format(quote(path), cmd))
node.run("zfs unmount {}".format(quote(path)))
def create_raidz(node, path, devices, raid='raidz'):
cmd = ""
actual_targets = []
for device in devices:
actual_targets.append(quote(prepare_blockdevice(node, device)))
cmd += "{} {} ".format(raid, " ".join(actual_targets))
node.run("zpool create {} {}".format(quote(path), cmd))
node.run("zfs unmount {}".format(quote(path)))
def create_single(node, path, device):
actual_target = prepare_blockdevice(node, device)
node.run("zpool create {} {}".format(quote(path), quote(actual_target)))
node.run("zfs unmount {}".format(quote(path)))
def does_exist(node, path):
status_result = node.run(
"zpool list {}".format(quote(path)),
may_fail=True,
)
return status_result.return_code == 0
def prepare_blockdevice(node, device):
# To increase our chances of success, we run partprobe beforehand to
# make the kernel re-scan all devices.
@ -92,53 +55,68 @@ class ZFSPool(Item):
"""
BUNDLE_ATTRIBUTE_NAME = "zfs_pools"
ITEM_ATTRIBUTES = {
'device': None,
'mirrors': None,
'raidz': None,
'raidz2': None,
'raidz3': None,
'config': None,
'autotrim': None,
'autoreplace': None,
'autoexpand': None,
}
ITEM_TYPE_NAME = "zfs_pool"
def __repr__(self):
return "<ZFSPool name:{} device:{} mirrors:{} raidz:{}>".format(
return "<ZFSPool name:{} autoexpand:{} autoreplace:{} autotrim:{} config:{}>".format(
self.name,
self.attributes['device'],
self.attributes['mirrors'],
self.attributes['raidz'],
self.attributes['autoexpand'],
self.attributes['autoreplace'],
self.attributes['autotrim'],
self.attributes['config'],
)
def cdict(self):
return {}
ret = {}
for i in {'autoexpand', 'autoreplace', 'autotrim'}:
if self.attributes.get(i):
ret[i] = self.attributes[i]
return ret
@property
def devices_used(self):
devices = []
if self.attributes['device'] is not None:
devices.append(self.attributes['device'])
if self.attributes['mirrors'] is not None:
for mirror in self.attributes['mirrors']:
devices.extend(mirror)
if self.attributes['raidz'] is not None:
devices.extend(self.attributes['raidz'])
return devices
devices = set()
for option in self.attributes['config']:
for device in option['devices']:
devices.add(device)
return sorted(devices)
def fix(self, status):
if status.must_be_created:
if self.attributes['device'] is not None:
create_single(self.node, self.name, self.attributes['device'])
elif self.attributes['mirrors'] is not None:
create_mirrors(self.node, self.name, self.attributes['mirrors'])
elif self.attributes['raidz'] is not None:
create_raidz(self.node, self.name, self.attributes['raidz'])
elif self.attributes['raidz2'] is not None:
create_raidz(self.node, self.name, self.attributes['raidz'], 'raidz2')
elif self.attributes['raidz2'] is not None:
create_raidz(self.node, self.name, self.attributes['raidz'], 'raidz3')
cmdline = []
for option in self.attributes['config']:
if option.get('type'):
cmdline.append(option['type'])
if option['type'] == 'log' and len(option['devices']) > 1:
cmdline.append('mirror')
for device in sorted(option['devices']):
cmdline.append(quote(prepare_blockdevice(node, device)))
self.run('zpool create {} {}'.format(quote(self.name), ' '.join(cmdline)))
for attr in {'autoexpand', 'autoreplace', 'autotrim'}:
if attr in status.keys_to_fix:
state_str = 'on' if status.cdict[attr] else 'off'
self.run('zpool set {}={} {}'.format(attr, state_str, quote(self.name)))
def sdict(self):
# We don't care about the device if the pool already exists.
return {} if does_exist(self.node, self.name) else None
status_result = self.run('zpool list {}'.format(quote(self.name)), may_fail=True)
if status_result.return_code != 0:
return {}
autoexpand_state = self.run('zpool get autoexpand -H -o value {}'.format(quote(self.name)), may_fail=True).stdout.decode().strip()
autoreplace_state = self.run('zpool get autoreplace -H -o value {}'.format(quote(self.name)), may_fail=True).stdout.decode().strip()
autotrim_state = self.run('zpool get autotrim -H -o value {}'.format(quote(self.name)), may_fail=True).stdout.decode().strip()
return {
'autoexpand': (autoexpand_state == 'on'),
'autoreplace': (autoreplace_state == 'on'),
'autotrim': (autotrim_state == 'on'),
}
def test(self):
duplicate_devices = [
@ -173,15 +151,40 @@ class ZFSPool(Item):
@classmethod
def validate_attributes(cls, bundle, item_id, attributes):
device_config = []
for key in ('device', 'mirrors', 'raidz', 'raidz2', 'raidz3'):
device_config.append(attributes.get(key))
device_config = [key for key in device_config if key is not None]
if len(device_config) != 1:
if not isinstance(attributes['config'], list):
raise BundleError(_(
"{item} on node {node} must have exactly one of "
"'device', 'mirrors', 'raidz', 'raidz2' or 'raidz3'"
"{item} on node {node} 'config'"
).format(
item=item_id,
node=bundle.node.name,
))
for config in attributes['config']:
if not config.get('type', '') in {'', 'mirror', 'raidz', 'raidz2', 'raidz3', 'cache', 'log'}:
raise BundleError(_(
"{item} on node {node} has invalid type '{type}', "
"must be one of (unset), 'mirror', 'raidz', 'raidz2', "
"'raidz3', 'cache', 'log'"
).format(
item=item_id,
node=bundle.node.name,
type=config['type'],
))
if not config.get('devices', set()):
raise BundleError(_(
"{item} on node {node} uses no devices!"
).format(
item=item_id,
node=bundle.node.name,
))
if config.get('type') == 'log':
if not 0 < len(config['devices']) < 3:
raise BundleError(_(
"{item} on node {node} type 'log' must use exactly "
"one or two devices"
).format(
item=item_id,
node=bundle.node.name,
))