Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hotsos/core: add name aliasing support #929

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 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,89 @@ 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)

Alias Naming
------------

An alias can be anything. The only limitation is that an alias cannot start with the
name of the main package (i.e., `hotsos.`). This is to prevent mixing up aliases with
the real property paths. The alias registration will fail with `AliasForbiddenError(...)`
in such cases.

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

105 changes: 105 additions & 0 deletions hotsos/core/alias.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"""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 AliasForbiddenError(Exception):
"""Raised when an alias is forbidden to use."""

def __init__(self, name):
self.message = f"Alias '{name}` is forbidden!"

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.
AliasForbiddenError: If the alias name starts with "hotsos."
"""
isprop = isinstance(decoratee, property)
target = decoratee.fget if isprop else decoratee

if name.startswith("hotsos."):
raise AliasForbiddenError(name)

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 @@ -10,6 +10,7 @@
)
from hotsos.core.factory import FactoryBase
from hotsos.core.host_helpers.cli import CLIHelperFile
from hotsos.core.alias import alias


@dataclass
Expand Down Expand Up @@ -81,6 +82,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 @@ -7,6 +7,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

lower_bound_ops = ["gt", "ge", "eq"] # ops that define a lower bound
upper_bound_ops = ["lt", "le", "eq"] # ops that define an upper bound
Expand Down Expand Up @@ -495,6 +496,7 @@ class AptPackage:
version: str


@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 @@ -19,6 +19,7 @@
from hotsos.core.host_helpers import CLIHelper, CLIHelperFile
from hotsos.core.host_helpers.common import ServiceManagerBase
from hotsos.core.log import log
from hotsos.core.alias import alias


class SystemdService():
Expand Down Expand Up @@ -357,6 +358,7 @@ def _service_filtered_ps(self):
return ps_filtered


@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 @@ -11,6 +11,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 @@ -176,6 +177,7 @@ class JujuCharm:
version: int


@alias('juju.base')
class JujuBase():
""" Juju checks base class. """
CHARM_MANIFEST_GLOB = "agents/unit-*/state/deployer/manifests"
Expand Down Expand Up @@ -259,6 +261,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
Loading
Loading