Skip to content

Commit

Permalink
Extend pool listing to show alert if device size changed
Browse files Browse the repository at this point in the history
Signed-off-by: mulhern <[email protected]>
  • Loading branch information
mulkieran committed Oct 13, 2022
1 parent 6729a26 commit 7e09a9f
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 33 deletions.
4 changes: 2 additions & 2 deletions docs/stratis.txt
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ pool overprovision <pool name> <(yes|no)> ::
available.
pool explain <code> ::
Explain any code that might show up in the Alerts column when
listing a pool. Codes may be prefixed with a "W", for "warning", or an "E",
for "error".
listing a pool. Codes may be prefixed with an "I" for "info", a "W" for
"warning", or an "E" for "error".
pool debug get-object-path <(--uuid <uuid> |--name <name>)> ::
Look up the D-Bus object path for a pool given the UUID or name.
filesystem create <pool_name> <fs_name> [<fs_name>..] [--size <size>]::
Expand Down
137 changes: 107 additions & 30 deletions src/stratis_cli/_actions/_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"""
Pool actions.
"""
# pylint: disable=too-many-lines

# isort: STDLIB
import os
Expand All @@ -25,7 +26,11 @@
from justbytes import Range

from .._constants import YesOrNo
from .._error_codes import PoolAllocSpaceErrorCode, PoolErrorCode
from .._error_codes import (
PoolAllocSpaceErrorCode,
PoolDeviceSizeChangeCode,
PoolErrorCode,
)
from .._errors import (
StratisCliEngineError,
StratisCliFsLimitChangeError,
Expand Down Expand Up @@ -723,34 +728,30 @@ def my_func(value):
return _List._maybe_inconsistent(value, my_func)

@staticmethod
def alert_string(mopool):
def alert_string(codes):
"""
Alert information to display, if any
:param mopool: object to access pool properties
:param codes: list of error codes to display
:type codes: list of PoolErrorCode
:returns: string w/ alert information, "" if no alert
:rtype: str
"""
error_codes = _List.alert_codes(mopool)

return ", ".join(sorted(str(code) for code in error_codes))
return ", ".join(sorted(str(code) for code in codes))

@staticmethod
def alert_summary(mopool):
def alert_summary(codes):
"""
Alert summary to display, if any
:param mopool: object to access pool properties
:param codes: list of error codes to display
:type codes: list of PoolErrorCode
:returns: string with alert summary
:rtype: str
"""
error_codes = _List.alert_codes(mopool)

output = [f" {str(code)}: {code.summarize()}" for code in error_codes]
output.insert(0, str(len(error_codes)))

return output
return [f"{str(code)}: {code.summarize()}" for code in codes]

@staticmethod
def alert_codes(mopool):
Expand All @@ -768,22 +769,84 @@ def alert_codes(mopool):
[PoolAllocSpaceErrorCode.NO_ALLOC_SPACE] if mopool.NoAllocSpace() else []
)

error_codes = availability_error_codes + no_alloc_space_error_codes
return availability_error_codes + no_alloc_space_error_codes

@staticmethod
def _pools_with_changed_devs(devs_to_search):
"""
Returns a tuple of sets containing (1) pools that have a device that
has increased in size and (2) pools that have a device that has
decreased in size.
A pool may occupy both sets if one device has increased and one has
decreased.
:param devs_to_search: an iterable of device objects
:returns: a pair of sets
:rtype: tuple of (set of ObjectPath)
"""
# pylint: disable=import-outside-toplevel
from ._data import MODev

(increased, decreased) = (set(), set())
for (_, info) in devs_to_search:
modev = MODev(info)
size = Range(modev.TotalPhysicalSize())
observed_size = get_property(modev.NewPhysicalSize(), Range, size)
if observed_size > size: # pragma: no cover
increased.add(modev.Pool())
if observed_size < size: # pragma: no cover
decreased.add(modev.Pool())

return (increased, decreased)

return error_codes
@staticmethod
def _from_sets(pool_object_path, increased, decreased):
"""
Get the code from sets and one pool object path.
:param pool_object_path: the pool object path
:param increased: pools that have devices that have increased in size
:type increased: set of object path
:param decreased: pools that have devices that have decrease in size
:type increased: set of object path
:returns: the codes
"""
if (
pool_object_path in increased and pool_object_path in decreased
): # pragma: no cover
return [
PoolDeviceSizeChangeCode.DEVICE_SIZE_INCREASED,
PoolDeviceSizeChangeCode.DEVICE_SIZE_DECREASED,
]
if pool_object_path in increased: # pragma: no cover
return [PoolDeviceSizeChangeCode.DEVICE_SIZE_INCREASED]
if pool_object_path in decreased: # pragma: no cover
return [PoolDeviceSizeChangeCode.DEVICE_SIZE_DECREASED]
return []

def _print_detail_view(self, pool_uuid, mopool):
def _print_detail_view(self, pool_uuid, mopool, size_change_codes):
"""
Print the detailed view for a single pool.
:param UUID uuid: the pool uuid
:param MOPool mopool: properties of the pool
:param size_change_codes: size change codes
:type size_change_codes: list of PoolDeviceSizeChangeCode
"""
encrypted = mopool.Encrypted()

print(f"UUID: {self.uuid_formatter(pool_uuid)}")
print(f"Name: {mopool.Name()}")
print(f"Alerts: {os.linesep.join(_List.alert_summary(mopool))}")

alert_summary = _List.alert_summary(
_List.alert_codes(mopool) + size_change_codes
)
print(f"Alerts: {str(len(alert_summary))}")
for line in alert_summary: # pragma: no cover
print(f" {line}")

print(
f"Actions Allowed: "
f"{PoolActionAvailability.from_str(mopool.AvailableActions())}"
Expand Down Expand Up @@ -823,12 +886,12 @@ def _print_detail_view(self, pool_uuid, mopool):

print(f" Used: {total_physical_used_str}")

def list_pools_default(self, *, pool_uuid=None):
def list_pools_default(self, *, pool_uuid=None): # pylint: disable=too-many-locals
"""
List all pools that are listed by default. These are all started pools.
"""
# pylint: disable=import-outside-toplevel
from ._data import MOPool, ObjectManager, pools
from ._data import MOPool, ObjectManager, devs, pools

proxy = get_object(TOP_OBJECT)

Expand Down Expand Up @@ -888,8 +951,13 @@ def gen_string(has_property, code):

managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})
if pool_uuid is None:
(increased, decreased) = _List._pools_with_changed_devs(
devs().search(managed_objects)
)

pools_with_props = [
MOPool(info) for objpath, info in pools().search(managed_objects)
(objpath, MOPool(info))
for objpath, info in pools().search(managed_objects)
]

tables = [
Expand All @@ -898,9 +966,12 @@ def gen_string(has_property, code):
physical_size_triple(mopool),
properties_string(mopool),
self.uuid_formatter(mopool.Uuid()),
_List.alert_string(mopool),
_List.alert_string(
_List.alert_codes(mopool)
+ _List._from_sets(pool_object_path, increased, decreased)
),
)
for mopool in pools_with_props
for (pool_object_path, mopool) in pools_with_props
]

print_table(
Expand All @@ -917,15 +988,21 @@ def gen_string(has_property, code):

else:
this_uuid = pool_uuid.hex
mopool = MOPool(
next(
pools(props={"Uuid": this_uuid})
.require_unique_match(True)
.search(managed_objects)
)[1]
(pool_object_path, mopool) = next(
pools(props={"Uuid": this_uuid})
.require_unique_match(True)
.search(managed_objects)
)

(increased, decreased) = _List._pools_with_changed_devs(
devs(props={"Pool": pool_object_path}).search(managed_objects)
)

device_change_codes = _List._from_sets(
pool_object_path, increased, decreased
)

self._print_detail_view(pool_uuid, mopool)
self._print_detail_view(pool_uuid, MOPool(mopool), device_change_codes)

def list_stopped_pools(self, *, pool_uuid=None):
"""
Expand Down
67 changes: 66 additions & 1 deletion src/stratis_cli/_error_codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,77 @@ def from_str(code_str):
)


class PoolDeviceSizeChangeCode(IntEnum):
"""
Codes for identifying for a pool if a device that belongs to the pool has
been detected to have increased or reduced in size.
"""

DEVICE_SIZE_INCREASED = 1
DEVICE_SIZE_DECREASED = 2

def __str__(self):
if self is PoolDeviceSizeChangeCode.DEVICE_SIZE_INCREASED:
return f"IDS{str(self.value).zfill(3)}"

if self is PoolDeviceSizeChangeCode.DEVICE_SIZE_DECREASED:
return f"WDS{str(self.value).zfill(3)}"

assert False, "impossible error code reached" # pragma: no cover

def explain(self):
"""
Return an explanation of the return code.
"""
if self is PoolDeviceSizeChangeCode.DEVICE_SIZE_INCREASED:
return (
"At least one device belonging to this pool appears to have "
"increased in size."
)

if self is PoolDeviceSizeChangeCode.DEVICE_SIZE_DECREASED:
return (
"At least one device belonging to this pool appears to have "
"decreased in size."
)

assert False, "impossible error code reached" # pragma: no cover

def summarize(self):
"""
Return a short summary of the return code.
"""
if self is PoolDeviceSizeChangeCode.DEVICE_SIZE_INCREASED:
return "A device in this pool has increased in size."

if self is PoolDeviceSizeChangeCode.DEVICE_SIZE_DECREASED:
return "A device in this pool has decreased in size."

assert False, "impossible error code reached" # pragma: no cover

@staticmethod
def from_str(code_str):
"""
Discover the code, if any, from the code string.
:returns: the code if it finds a match, otherwise None
:rtype: PoolAllocSpaceErrorCode or NoneType
"""
return next(
(code for code in PoolDeviceSizeChangeCode if code_str == str(code)), None
)


class PoolErrorCode:
"""
Summary class for all pool error codes.
"""

CLASSES = [PoolMaintenanceErrorCode, PoolAllocSpaceErrorCode]
CLASSES = [
PoolMaintenanceErrorCode,
PoolAllocSpaceErrorCode,
PoolDeviceSizeChangeCode,
]

@staticmethod
def codes():
Expand Down
24 changes: 24 additions & 0 deletions tests/whitebox/unittest/test_error_fmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import unittest

# isort: LOCAL
from stratis_cli._error_codes import PoolDeviceSizeChangeCode
from stratis_cli._errors import (
StratisCliGenerationError,
StratisCliIncoherenceError,
Expand Down Expand Up @@ -69,3 +70,26 @@ def test_stratis_cli_partial_failure_error(self):
"action", "unique resource", "something failed"
)
)


class SummarizeTestCase(unittest.TestCase):
"""
Test summarize() function.
"""

def _summarize_test(self, summary_value):
"""
Check that the summary value is a str and is non-empty.
:param summary_value: the result of calling summary()
"""
self.assertIsInstance(summary_value, str)
self.assertNotEqual(summary_value, "")

def test_pool_device_size_change_code(self):
"""
Verify valid strings returned from summary() method.
"""

self._summarize_test(PoolDeviceSizeChangeCode.DEVICE_SIZE_INCREASED.summarize())
self._summarize_test(PoolDeviceSizeChangeCode.DEVICE_SIZE_DECREASED.summarize())

0 comments on commit 7e09a9f

Please sign in to comment.