Mini Shell
# devices/stratis.py
#
# Copyright (C) 2020 Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details. You should have received a copy of the
# GNU General Public License along with this program; if not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
# Red Hat Author(s): Vojtech Trefny <vtrefny@redhat.com>
#
import os
import logging
log = logging.getLogger("blivet")
from .storage import StorageDevice
from ..static_data import stratis_info
from ..storage_log import log_method_call
from ..errors import DeviceError, StratisError
from ..size import Size
from ..tasks import availability
from .. import devicelibs
class StratisPoolDevice(StorageDevice):
""" A stratis pool device """
_type = "stratis pool"
_resizable = False
_packages = ["stratisd", "stratis-cli"]
_dev_dir = "/dev/stratis"
_format_immutable = True
_external_dependencies = [availability.STRATISPREDICTUSAGE_APP, availability.STRATIS_DBUS]
def __init__(self, *args, **kwargs):
"""
:encrypted: whether this pool is encrypted or not
:type encrypted: bool
:keyword passphrase: device passphrase
:type passphrase: str
:keyword key_file: path to a file containing a key
:type key_file: str
"""
self._encrypted = kwargs.pop("encrypted", False)
self.__passphrase = kwargs.pop("passphrase", None)
self._key_file = kwargs.pop("key_file", None)
super(StratisPoolDevice, self).__init__(*args, **kwargs)
@property
def blockdevs(self):
""" A list of this pool block devices """
return self.parents[:]
@property
def filesystems(self):
""" A list of this pool block filesystems """
return self.children[:]
@property
def size(self):
""" The size of this pool """
# sum up the sizes of the block devices
return sum(parent.size for parent in self.parents)
@property
def _physical_size(self):
if self.exists:
pool_info = stratis_info.get_pool_info(self.name)
if not pool_info:
raise DeviceError("Failed to get information about pool %s" % self.name)
return pool_info.physical_size
else:
return self.size
@property
def _pool_metadata_size(self):
return devicelibs.stratis.pool_used([bd.size for bd in self.blockdevs],
self.encrypted)
@property
def _physical_used(self):
physical_used = Size(0)
# filesystems
for filesystem in self.filesystems:
physical_used += filesystem.used_size
# pool metadata
physical_used += self._pool_metadata_size
return physical_used
@property
def free_space(self):
""" Free space in the pool usable for new filesystems """
return self._physical_size - self._physical_used
@property
def encrypted(self):
""" True if this device is encrypted. """
return self._encrypted
@encrypted.setter
def encrypted(self, encrypted):
self._encrypted = encrypted
@property
def key_file(self):
""" Path to key file to be used in /etc/crypttab """
return self._key_file
def _set_passphrase(self, passphrase):
""" Set the passphrase used to access this device. """
self.__passphrase = passphrase
passphrase = property(fset=_set_passphrase)
@property
def has_key(self):
return ((self.__passphrase not in ["", None]) or
(self._key_file and os.access(self._key_file, os.R_OK)))
def _pre_create(self):
super(StratisPoolDevice, self)._pre_create()
if self.encrypted and not self.has_key:
raise StratisError("cannot create encrypted stratis pool without key")
def _create(self):
""" Create the device. """
log_method_call(self, self.name, status=self.status)
bd_list = [bd.path for bd in self.parents]
devicelibs.stratis.create_pool(name=self.name,
devices=bd_list,
encrypted=self.encrypted,
passphrase=self.__passphrase,
key_file=self._key_file)
def _post_create(self):
super(StratisPoolDevice, self)._post_create()
self.format.exists = True
pool_info = stratis_info.get_pool_info(self.name)
if not pool_info:
raise DeviceError("Failed to get information about newly created pool %s" % self.name)
self.uuid = pool_info.uuid
for parent in self.parents:
parent.format.pool_name = self.name
parent.format.pool_uuid = self.uuid
def _destroy(self):
""" Destroy the device. """
log_method_call(self, self.name, status=self.status)
devicelibs.stratis.remove_pool(self.uuid)
def add_hook(self, new=True):
super(StratisPoolDevice, self).add_hook(new=new)
if new:
return
for parent in self.parents:
parent.format.pool_name = self.name
parent.format.pool_uuid = self.uuid
def remove_hook(self, modparent=True):
if modparent:
for parent in self.parents:
parent.format.pool_name = None
parent.format.pool_uuid = None
super(StratisPoolDevice, self).remove_hook(modparent=modparent)
def dracut_setup_args(self):
return set(["stratis.rootfs.pool_uuid=%s" % self.uuid])
class StratisFilesystemDevice(StorageDevice):
""" A stratis pool device """
_type = "stratis filesystem"
_resizable = False
_packages = ["stratisd", "stratis-cli"]
_dev_dir = "/dev/stratis"
_external_dependencies = [availability.STRATISPREDICTUSAGE_APP, availability.STRATIS_DBUS]
_min_size = Size("512 MiB")
def __init__(self, name, parents=None, size=None, uuid=None, exists=False):
if not exists and parents[0].free_space <= devicelibs.stratis.filesystem_md_size(size):
raise StratisError("cannot create new stratis filesystem, not enough free space in the pool")
super(StratisFilesystemDevice, self).__init__(name=name, size=size, uuid=uuid,
parents=parents, exists=exists)
def _get_name(self):
""" This device's name. """
if self.pool is not None:
return "%s/%s" % (self.pool.name, self._name)
else:
return super(StratisFilesystemDevice, self)._get_name()
@property
def fsname(self):
""" The Stratis filesystem name (not including pool name). """
return self._name
@property
def pool(self):
if not self.parents:
# this should never happen but just to be sure
return None
return self.parents[0]
@property
def used_size(self):
""" Size used by this filesystem in the pool """
if not self.exists:
return devicelibs.stratis.filesystem_md_size(self.size)
else:
fs_info = stratis_info.get_filesystem_info(self.pool.name, self.fsname)
if not fs_info:
raise DeviceError("Failed to get information about filesystem %s" % self.name)
return fs_info.used_size
def _set_size(self, newsize):
log_method_call(self, self.name,
status=self.status, size=self._size, newsize=newsize)
if not isinstance(newsize, Size):
raise ValueError("new size must of type Size")
if not self.exists:
md_size = devicelibs.stratis.filesystem_md_size(newsize)
if md_size > self.pool.free_space:
raise DeviceError("not enough free space in pool")
super(StratisFilesystemDevice, self)._set_size(newsize)
def _create(self):
""" Create the device. """
log_method_call(self, self.name, status=self.status)
devicelibs.stratis.create_filesystem(name=self.fsname, pool_uuid=self.pool.uuid,
fs_size=self.size)
def _post_create(self):
super(StratisFilesystemDevice, self)._post_create()
fs_info = stratis_info.get_filesystem_info(self.pool.name, self.fsname)
if not fs_info:
raise DeviceError("Failed to get information about newly created filesystem %s" % self.name)
self.uuid = fs_info.uuid
self.format.pool_uuid = fs_info.pool_uuid
def _destroy(self):
""" Destroy the device. """
log_method_call(self, self.name, status=self.status)
devicelibs.stratis.remove_filesystem(self.pool.uuid, self.uuid)
def dracut_setup_args(self):
return set(["root=%s" % self.path])