Mini Shell
# stratis_info.py
# Backend code for populating a DeviceTree.
#
# 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 Lesser 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 Lesser General Public License for more details. You should have
# received a copy of the GNU Lesser 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 Lesser 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 uuid
from collections import namedtuple
from .. import safe_dbus
from ..size import Size
import logging
log = logging.getLogger("blivet")
# XXX we can't import these from devicelibs.stratis, circular imports make python mad
STRATIS_SERVICE = "org.storage.stratis3"
STRATIS_PATH = "/org/storage/stratis3"
STRATIS_POOL_INTF = STRATIS_SERVICE + ".pool.r0"
STRATIS_FILESYSTEM_INTF = STRATIS_SERVICE + ".filesystem.r0"
STRATIS_BLOCKDEV_INTF = STRATIS_SERVICE + ".blockdev.r0"
STRATIS_MANAGER_INTF = STRATIS_SERVICE + ".Manager.r0"
StratisPoolInfo = namedtuple("StratisPoolInfo", ["name", "uuid", "physical_size", "physical_used", "object_path", "encrypted"])
StratisFilesystemInfo = namedtuple("StratisFilesystemInfo", ["name", "uuid", "used_size", "pool_name",
"pool_uuid", "object_path"])
StratisBlockdevInfo = namedtuple("StratisBlockdevInfo", ["path", "uuid", "pool_name", "pool_uuid", "object_path"])
StratisLockedPoolInfo = namedtuple("StratisLockedPoolInfo", ["uuid", "key_desc", "devices"])
class StratisInfo(object):
""" Class to be used as a singleton.
Maintains the Stratis devices info cache.
"""
def __init__(self):
self._info_cache = None
def _get_pool_info(self, pool_path):
try:
properties = safe_dbus.get_properties_sync(STRATIS_SERVICE,
pool_path,
STRATIS_POOL_INTF)[0]
except safe_dbus.DBusPropertyError as e:
log.error("Error when getting DBus properties of '%s': %s",
pool_path, str(e))
if not properties:
log.error("Failed to get DBus properties of '%s'", pool_path)
return None
pool_size = properties.get("TotalPhysicalSize", 0)
valid, pool_used = properties.get("TotalPhysicalUsed",
(False, "TotalPhysicalUsed not available"))
if not valid:
log.warning("Failed to get Stratis pool physical used for %s: %s",
properties["Name"], pool_used)
pool_used = 0
return StratisPoolInfo(name=properties["Name"], uuid=properties["Uuid"],
physical_size=Size(pool_size), physical_used=Size(pool_used),
object_path=pool_path, encrypted=properties["Encrypted"])
def _get_filesystem_info(self, filesystem_path):
try:
properties = safe_dbus.get_properties_sync(STRATIS_SERVICE,
filesystem_path,
STRATIS_FILESYSTEM_INTF)[0]
except safe_dbus.DBusPropertyError as e:
log.error("Error when getting DBus properties of '%s': %s",
filesystem_path, str(e))
if not properties:
log.error("Failed to get DBus properties of '%s'", filesystem_path)
return None
pool_info = self._get_pool_info(properties["Pool"])
if not pool_info:
return None
valid, used_size = properties.get("Used",
(False, "Used not available"))
if not valid:
log.warning("Failed to get Stratis filesystem used size for %s: %s",
properties["Name"], used_size)
used_size = 0
return StratisFilesystemInfo(name=properties["Name"], uuid=properties["Uuid"],
used_size=Size(used_size),
pool_name=pool_info.name, pool_uuid=pool_info.uuid,
object_path=filesystem_path)
def _get_blockdev_info(self, blockdev_path):
try:
properties = safe_dbus.get_properties_sync(STRATIS_SERVICE,
blockdev_path,
STRATIS_BLOCKDEV_INTF)[0]
except safe_dbus.DBusPropertyError as e:
log.error("Error when getting DBus properties of '%s': %s",
blockdev_path, str(e))
if not properties:
log.error("Failed to get DBus properties of '%s'", blockdev_path)
return None
blockdev_uuid = str(uuid.UUID(properties["Uuid"]))
pool_path = properties["Pool"]
if pool_path == "/":
pool_name = ""
return StratisBlockdevInfo(path=properties["Devnode"], uuid=blockdev_uuid,
pool_name="", pool_uuid="", object_path=blockdev_path)
else:
pool_info = self._get_pool_info(properties["Pool"])
if not pool_info:
return None
pool_name = pool_info.name
return StratisBlockdevInfo(path=properties["Devnode"], uuid=blockdev_uuid,
pool_name=pool_name, pool_uuid=pool_info.uuid,
object_path=blockdev_path)
def _get_locked_pools_info(self):
locked_pools = []
try:
pools_info = safe_dbus.get_property_sync(STRATIS_SERVICE,
STRATIS_PATH,
STRATIS_MANAGER_INTF,
"LockedPools")[0]
except safe_dbus.DBusCallError as e:
log.error("Failed to get list of locked Stratis pools: %s", str(e))
return locked_pools
for pool_uuid in pools_info.keys():
valid, (_err, description) = pools_info[pool_uuid]["key_description"]
if not valid:
log.info("Locked Stratis pool %s doesn't have a valid key description: %s", pool_uuid, description)
description = None
info = StratisLockedPoolInfo(uuid=pool_uuid,
key_desc=description,
devices=[d["devnode"] for d in pools_info[pool_uuid]["devs"]])
locked_pools.append(info)
return locked_pools
def _get_stratis_info(self):
self._info_cache = dict()
self._info_cache["pools"] = dict()
self._info_cache["blockdevs"] = dict()
self._info_cache["filesystems"] = dict()
self._info_cache["locked_pools"] = []
try:
ret = safe_dbus.check_object_available(STRATIS_SERVICE, STRATIS_PATH)
except safe_dbus.DBusCallError:
log.warning("Stratis DBus service is not running")
return
else:
if not ret:
log.warning("Stratis DBus service is not available")
objects = safe_dbus.call_sync(STRATIS_SERVICE,
STRATIS_PATH,
"org.freedesktop.DBus.ObjectManager",
"GetManagedObjects",
None)[0]
for path, interfaces in objects.items():
if STRATIS_POOL_INTF in interfaces.keys():
pool_info = self._get_pool_info(path)
if pool_info:
self._info_cache["pools"][pool_info.uuid] = pool_info
if STRATIS_FILESYSTEM_INTF in interfaces.keys():
fs_info = self._get_filesystem_info(path)
if fs_info:
self._info_cache["filesystems"][fs_info.uuid] = fs_info
if STRATIS_BLOCKDEV_INTF in interfaces.keys():
bd_info = self._get_blockdev_info(path)
if bd_info:
self._info_cache["blockdevs"][bd_info.uuid] = bd_info
self._info_cache["locked_pools"] = self._get_locked_pools_info()
@property
def pools(self):
if self._info_cache is None:
self._get_stratis_info()
return self._info_cache["pools"]
@property
def filesystems(self):
if self._info_cache is None:
self._get_stratis_info()
return self._info_cache["filesystems"]
@property
def blockdevs(self):
if self._info_cache is None:
self._get_stratis_info()
return self._info_cache["blockdevs"]
@property
def locked_pools(self):
if self._info_cache is None:
self._get_stratis_info()
return self._info_cache["locked_pools"]
def drop_cache(self):
self._info_cache = None
def get_pool_info(self, pool_name):
for pool in self.pools.values():
if pool.name == pool_name:
return pool
def get_filesystem_info(self, pool_name, fs_name):
for fs in self.filesystems.values():
if fs.pool_name == pool_name and fs.name == fs_name:
return fs
def get_blockdev_info(self, bd_path):
for bd in self.blockdevs.values():
if bd.path == bd_path or bd.path == os.path.realpath(bd_path):
return bd
stratis_info = StratisInfo()