Skip to content
/ hotsos Public
forked from canonical/hotsos

Commit

Permalink
hotsos/core: add name aliasing support
Browse files Browse the repository at this point in the history
at the moment, scenarios are using the long import paths in order to
reference to a Python property. this feature allows assigning an alias
to a Python class or variable in order to make using plugin property
interfaces easier.

Added aliases to the plugins.
Updated existing scenarios to use aliases.

Fixes canonical#912

Signed-off-by: Mustafa Kemal Gilor <[email protected]>
  • Loading branch information
xmkg committed Jul 18, 2024
1 parent b3bd3ae commit ddc4d2a
Show file tree
Hide file tree
Showing 136 changed files with 533 additions and 257 deletions.
76 changes: 76 additions & 0 deletions doc/source/contrib/language_ref/property_ref/main_properties.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Or they can reference a Python property. This is done by prefixing the import st
vars:
foo: '@path.to.myproperty'
bar: '@property.alias'
A :ref:`factory <FactoryClasses>` reference can also be defined using the following form:

Expand Down Expand Up @@ -46,6 +47,81 @@ Variables are accessible from any property within the file in which they are def

NOTE: global properties are not yet supported.

Aliases
=======

Aliasing provides easier access to a class or property. The class or property needs to
be aliased in order to use this feature.

.. code-block:: python
class MyHelperClass:
@property
def my_awesome_property(self):
return True
Normally, if the user wants to retrieve the value of the my_awesome_property in a YAML scenario
they have to write the whole import path to the class, as follows:

.. code-block:: yaml
vars:
foo: '@hotsos.module.path.MyHelperClass.my_awesome_property'
checks:
foo_is_true:
requires:
varops: [[$foo], [eq, true]]
But, with the help of aliasing, we can make it easier to type and remember for the user:

.. code-block:: python
from hotsos.core.alias import alias
@alias("helper")
class MyHelperClass:
@property
def my_awesome_property(self):
return True
... so the previous example can be written as follows:

.. code-block:: yaml
vars:
foo: '@helper.my_awesome_property'
checks:
foo_is_true:
requires:
varops: [[$foo], [eq, true]]
Individual properties can be aliased too:

.. code-block:: python
from hotsos.core.alias import alias
class MyHelperClass:
@alias('awesomeness')
@property
def my_awesome_property(self):
return True
.. code-block:: yaml
vars:
awesome: '@awesomeness'
Aliasing works for the following constructs:

- Python property names
- Class names (e.g. Config handler class type name)

Checks
======

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ Usage:

.. code-block:: yaml
property: <import path to python property>
property: <import path to python property or alias>
or

.. code-block:: yaml
property:
path: <import path to python property>
path: <import path to python property or alias>
ops: OPS_LIST
Cache keys:
Expand Down Expand Up @@ -269,7 +269,7 @@ Usage:
.. code-block:: yaml
config:
handler: <import path>
handler: <import path or alias>
path: <path to config file>
assertions:
- allow-unset: <bool>
Expand All @@ -295,7 +295,7 @@ Example:
checks:
checkcfg:
config:
handler: hotsos.core.plugins.openstack.OpenstackConfig
handler: openstack.config
path: etc/nova/nova.conf
assertions:
- key: debug
Expand Down
2 changes: 1 addition & 1 deletion doc/source/contrib/scenarios.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ matches and the second is a fallback:
.. code-block:: yaml
vars:
mem_current: '@hotsos.core.host_helpers.systemd.ServiceFactory.memory_current:neverfail'
mem_current: '@systemd.service.memory_current:neverfail'
checks:
is_enabled:
systemd:
Expand Down
1 change: 1 addition & 0 deletions doc/source/contrib/writing_checks_overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,6 @@ the rest of the directory will only be run if it resolves to *True*:
requires:
or:
- property: hotsos.core.plugins.myplugin.mustbetrue
- property: alias.mustbetrue
- path: file/that/must/exist
91 changes: 91 additions & 0 deletions hotsos/core/alias.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""Aliasing utilities."""

from hotsos.core.log import log


class AliasAlreadyInUseError(Exception):
"""Raised when an alias is already in use."""

def __init__(self, name):
self.message = f"Alias '{name}` already in use!"

def __str__(self):
return self.message


class AliasRegistry:
"""
A class that provides a registry for aliasing Python things.
"""

# A class-level dictionary to store registered aliases.
registry = {}

@staticmethod
def register(name, decoratee):
"""
Register a function, method, or property under an alias.
This method handles different types of Python objects and creates
appropriate wrappers or registrations based on the object type.
Args:
name (str): The alias under which to register the decoratee.
decoratee (callable or property): The Python object to be
registered.
Raises:
AliasAlreadyInUseError: If the alias name is already registered.
"""
isprop = isinstance(decoratee, property)
target = decoratee.fget if isprop else decoratee

if name in AliasRegistry.registry:
log.debug("alias registration failed -- already in use(`%s`)",
name)
raise AliasAlreadyInUseError(name)

import_path = f"{target.__module__}.{target.__qualname__}"
log.debug("registering alias `%s` --> {%s}", name, import_path)
# Register full import path.
AliasRegistry.registry[name] = import_path

@staticmethod
def resolve(the_alias, default=None):
"""
Retrieve a registered alias.
Args:
the_alias (str): The alias to retrieve.
Returns:
callable: The function or wrapper associated with the alias.
Raises:
NoSuchAliasError: No such alias in the registry.
"""

if the_alias not in AliasRegistry.registry:
log.debug(
"alias `%s` not found in the registry, "
"returning the default value",
the_alias,
)
return default

value = AliasRegistry.registry[the_alias]
log.debug("alias %s resolved to %s", the_alias, value)
return value


def alias(argument):
"""Create an alias for a property, function or a thing."""

def real_decorator(func):
"""We're not wrapping the func as we don't want
to do anything at runtime. We just want to alias
`func` to some user-defined name and call it on-demand."""
AliasRegistry.register(argument, func)
return func

return real_decorator
4 changes: 4 additions & 0 deletions hotsos/core/host_helpers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,7 @@
AAProfileFactory,
ApparmorHelper,
)
from .filestat import ( # noqa: F403,F401
FileFactory,
FileObj
)
2 changes: 2 additions & 0 deletions hotsos/core/host_helpers/apparmor.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
)
from hotsos.core.factory import FactoryBase
from hotsos.core.host_helpers.cli import CLIHelperFile
from hotsos.core.alias import alias


class AAProfile():
Expand Down Expand Up @@ -77,6 +78,7 @@ def profiles_unconfined(self):
return self.profiles.get('unconfined', {}).get('profiles', [])


@alias('apparmor.profile')
class AAProfileFactory(FactoryBase):
"""
Dynamically create AAProfile objects using profile name.
Expand Down
2 changes: 2 additions & 0 deletions hotsos/core/host_helpers/filestat.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from hotsos.core.config import HotSOSConfig
from hotsos.core.factory import FactoryBase
from hotsos.core.log import log
from hotsos.core.alias import alias


class FileObj():
Expand Down Expand Up @@ -38,6 +39,7 @@ def size(self):
return size


@alias('file')
class FileFactory(FactoryBase):
"""
Factory to dynamically create FileObj objects using file path as input.
Expand Down
2 changes: 2 additions & 0 deletions hotsos/core/host_helpers/packaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from hotsos.core.host_helpers.cli import CLIHelper
from hotsos.core.log import log
from hotsos.core.utils import sorted_dict
from hotsos.core.alias import alias


class DPKGBadVersionSyntax(Exception):
Expand Down Expand Up @@ -483,6 +484,7 @@ def __init__(self, name, version):
self.version = version


@alias('apt.package')
class AptFactory(FactoryBase):
"""
Factory to dynamically get package versions.
Expand Down
2 changes: 2 additions & 0 deletions hotsos/core/host_helpers/ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from hotsos.core.factory import FactoryBase
from hotsos.core.host_helpers.cli import CLIHelper
from hotsos.core.log import log
from hotsos.core.alias import alias


class SSLCertificate():
Expand Down Expand Up @@ -65,6 +66,7 @@ def certificate_expires_soon(self):
return self.certificate.days_to_expire <= self.expire_days


@alias('sslcert')
class SSLCertificatesFactory(FactoryBase):
"""
Factory to dynamically create SSLCertificate objects for given paths.
Expand Down
2 changes: 2 additions & 0 deletions hotsos/core/host_helpers/systemd.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from hotsos.core.host_helpers.common import ServiceManagerBase
from hotsos.core.log import log
from hotsos.core.utils import sorted_dict
from hotsos.core.alias import alias


class SystemdService():
Expand Down Expand Up @@ -411,6 +412,7 @@ def summary(self):
'ps': self._process_info}


@alias("systemd.service")
class ServiceFactory(FactoryBase):
"""
Factory to dynamically create SystemdService objects for given services.
Expand Down
2 changes: 2 additions & 0 deletions hotsos/core/plugins/juju/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from hotsos.core.host_helpers import PebbleHelper, SystemdHelper
from hotsos.core.plugins.juju.resources import JujuBase
from hotsos.core import plugintools
from hotsos.core.alias import alias

SVC_VALID_SUFFIX = r'[0-9a-zA-Z-_]*'
JUJU_SVC_EXPRS = [rf'mongod{SVC_VALID_SUFFIX}',
Expand All @@ -12,6 +13,7 @@
rf'(?:^|[^\s])juju-db{SVC_VALID_SUFFIX}']


@alias('juju')
class JujuChecks(plugintools.PluginPartBase, JujuBase):
""" Juju checks. """
plugin_name = 'juju'
Expand Down
3 changes: 3 additions & 0 deletions hotsos/core/plugins/juju/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from hotsos.core.config import HotSOSConfig
from hotsos.core.log import log
from hotsos.core import utils
from hotsos.core.alias import alias


class JujuMachine():
Expand Down Expand Up @@ -174,6 +175,7 @@ def __init__(self, name, version):
self.version = int(version)


@alias('juju.base')
class JujuBase():
""" Juju checks base class. """
CHARM_MANIFEST_GLOB = "agents/unit-*/state/deployer/manifests"
Expand Down Expand Up @@ -257,6 +259,7 @@ def charm_names(self):
return list(self.charms.keys())


@alias('juju.bin')
class JujuBinaryInterface(JujuBase):
""" Interface to juju binary. """
@property
Expand Down
2 changes: 2 additions & 0 deletions hotsos/core/plugins/kernel/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
from hotsos.core import host_helpers, plugintools
from hotsos.core.config import HotSOSConfig
from hotsos.core.plugins.kernel.config import KernelConfig
from hotsos.core.alias import alias


@alias('kernel')
class KernelBase():
""" Base class for kernel plugin helpers. """
@cached_property
Expand Down
2 changes: 2 additions & 0 deletions hotsos/core/plugins/kernel/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from hotsos.core.config import HotSOSConfig
from hotsos.core import host_helpers
from hotsos.core.alias import alias


class KernelConfig(host_helpers.ConfigBase):
Expand Down Expand Up @@ -38,6 +39,7 @@ def _load(self):
break


@alias('kernel.systemdconfig')
class SystemdConfig(host_helpers.IniConfigBase):
"""Systemd configuration."""
def __init__(self, *args, **kwargs):
Expand Down
Loading

0 comments on commit ddc4d2a

Please sign in to comment.