Skip to content

Commit

Permalink
Detect if plugin is runnable as classmethod
Browse files Browse the repository at this point in the history
This permits us to check much earlier and without having to
instantiate the plugin which has a lot of pre-requisites.
  • Loading branch information
dosaboy committed Oct 21, 2024
1 parent 3f4a26d commit 7d54d4a
Show file tree
Hide file tree
Showing 44 changed files with 458 additions and 232 deletions.
4 changes: 2 additions & 2 deletions hotsos/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ class HotSOSSummary(plugintools.PluginPartBase):
plugin_root_index = 0
summary_part_index = 0

@property
def plugin_runnable(self):
@classmethod
def is_runnable(cls):
return True

@property
Expand Down
2 changes: 2 additions & 0 deletions hotsos/core/host_helpers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .common import InstallInfoBase
from .cli import (
CLIHelper,
CLIHelperFile,
Expand Down Expand Up @@ -43,6 +44,7 @@
CLIHelperFile.__name__,
ConfigBase.__name__,
IniConfigBase.__name__,
InstallInfoBase.__name__,
NetworkPort.__name__,
HostNetworkingHelper.__name__,
DPKGVersion.__name__,
Expand Down
26 changes: 25 additions & 1 deletion hotsos/core/host_helpers/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import pickle
import re
from functools import cached_property
from dataclasses import dataclass
from dataclasses import dataclass, fields

from searchkit.utils import MPCache
from hotsos.core.config import HotSOSConfig
Expand Down Expand Up @@ -374,3 +374,27 @@ def get_ps_axo_flags_available():

# strip data_root since it will be prepended later
return _paths[0].partition(HotSOSConfig.data_root)[2]


@dataclass
class InstallInfoBase():
"""
Provides a common way to define install information such as packages and
runtime services assocated with a plugin.
To use this class it should be re-implemented as a dataclass that
initialises the required attributes. It can then either be inherited or
mixed in to avoid issues with excessive inheritance.
"""
apt: None = None
pebble: None = None
snaps: None = None
systemd: None = None

def mixin(self, _self):
for attr in fields(self):
val = getattr(self, attr.name)
if val is None:
continue

setattr(_self, attr.name, val)
43 changes: 34 additions & 9 deletions hotsos/core/plugins/juju/common.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import os

from hotsos.core.host_helpers import PebbleHelper, SystemdHelper
from dataclasses import dataclass, field
from functools import cached_property

from hotsos.core.host_helpers import (
InstallInfoBase,
PebbleHelper,
SystemdHelper,
)
from hotsos.core.plugins.juju.resources import JujuBase
from hotsos.core import plugintools

Expand All @@ -12,17 +18,31 @@
rf'(?:^|[^\s])juju-db{SVC_VALID_SUFFIX}']


@dataclass
class JujuInstallInfo(InstallInfoBase):
""" Juju installation information. """
pebble: PebbleHelper = field(default_factory=lambda:
PebbleHelper(service_exprs=JUJU_SVC_EXPRS))
systemd: SystemdHelper = field(default_factory=lambda:
SystemdHelper(service_exprs=JUJU_SVC_EXPRS))


class JujuChecks(plugintools.PluginPartBase, JujuBase):
""" Juju checks. """
plugin_name = 'juju'
plugin_root_index = 12

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.pebble = PebbleHelper(service_exprs=JUJU_SVC_EXPRS)
self.systemd = SystemdHelper(service_exprs=JUJU_SVC_EXPRS)
# this is needed for juju scenarios
self.systemd_processes = self.systemd.processes
JujuInstallInfo().mixin(self)

@cached_property
def systemd_processes(self):
"""
Return a list of running processes related to the Juju service. This
is needed for Juju scenarios.
"""
return self.systemd.processes

@property
def version(self):
Expand All @@ -31,6 +51,11 @@ def version(self):

return "unknown"

@property
def plugin_runnable(self):
return os.path.exists(self.juju_lib_path)
@classmethod
def is_runnable(cls):
"""
Determine whether or not this plugin can and should be run.
@return: True or False
"""
return os.path.exists(cls.get_juju_lib_path())
17 changes: 9 additions & 8 deletions hotsos/core/plugins/juju/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,13 @@ class JujuBase():
""" Juju checks base class. """
CHARM_MANIFEST_GLOB = "agents/unit-*/state/deployer/manifests"

@property
def juju_lib_path(self):
@staticmethod
def get_juju_lib_path():
return os.path.join(HotSOSConfig.data_root, "var/lib/juju")

@cached_property
def machine(self):
machine = JujuMachine(self.juju_lib_path)
machine = JujuMachine(self.get_juju_lib_path())
if not machine.config:
log.debug("no juju machine identified")
return None
Expand All @@ -205,21 +205,22 @@ def units(self):
@return: dict of JujuUnit objects keyed by unit name.
"""
_units = {}
if not os.path.exists(self.juju_lib_path):
if not os.path.exists(self.get_juju_lib_path()):
return _units

if self.machine and self.machine.version >= "2.9":
_units = {u.name: u for u in self.machine.deployed_units}
else:
paths = glob.glob(os.path.join(self.juju_lib_path,
paths = glob.glob(os.path.join(self.get_juju_lib_path(),
"agents/unit-*"))
for unit in paths:
base = os.path.basename(unit)
ret = re.compile(r"unit-(\S+)-(\d+)").match(base)
if ret:
app = ret.group(1)
unit_id = ret.group(2)
u = JujuUnit(unit_id, app, self.juju_lib_path, path=unit)
u = JujuUnit(unit_id, app, self.get_juju_lib_path(),
path=unit)
_units[u.name] = u

return _units
Expand All @@ -232,10 +233,10 @@ def charms(self):
@return: dict of JujuCharm objects keyed by charm name.
"""
_charms = {}
if not os.path.exists(self.juju_lib_path):
if not os.path.exists(self.get_juju_lib_path()):
return _charms

for entry in glob.glob(os.path.join(self.juju_lib_path,
for entry in glob.glob(os.path.join(self.get_juju_lib_path(),
self.CHARM_MANIFEST_GLOB)):
name = None
versions = []
Expand Down
10 changes: 7 additions & 3 deletions hotsos/core/plugins/kernel/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,11 @@ class KernelChecks(KernelBase, plugintools.PluginPartBase):
plugin_name = 'kernel'
plugin_root_index = 15

@property
def plugin_runnable(self):
# Always run
@classmethod
def is_runnable(cls):
"""
Determine whether or not this plugin can and should be run.
@return: True or False
"""
return True
47 changes: 33 additions & 14 deletions hotsos/core/plugins/kubernetes.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import os
from dataclasses import dataclass, field
from functools import cached_property

from hotsos.core.config import HotSOSConfig
from hotsos.core.host_helpers import (
APTPackageHelper,
HostNetworkingHelper,
InstallInfoBase,
PebbleHelper,
SnapPackageHelper,
SystemdHelper,
Expand Down Expand Up @@ -42,10 +44,28 @@
]
# Snap-only deps
K8S_PACKAGE_DEPS_SNAP = [r'core[0-9]*']
K8S_SNAP_DEPS = K8S_PACKAGE_DEPS + K8S_PACKAGE_DEPS_SNAP


@dataclass
class KubernetesInstallInfo(InstallInfoBase):
""" Kubernetes installation information. """
apt: APTPackageHelper = field(default_factory=lambda:
APTPackageHelper(
core_pkgs=K8S_PACKAGES,
other_pkgs=K8S_PACKAGE_DEPS))
pebble: PebbleHelper = field(default_factory=lambda:
PebbleHelper(service_exprs=SERVICES))
snaps: SnapPackageHelper = field(default_factory=lambda:
SnapPackageHelper(
core_snaps=K8S_PACKAGES,
other_snaps=K8S_SNAP_DEPS))
systemd: SystemdHelper = field(default_factory=lambda:
SystemdHelper(service_exprs=SERVICES))


class KubernetesBase():
""" Base class for kuberenetes checks. """
""" Base class for Kubernetes checks. """
@cached_property
def flannel_ports(self):
ports = []
Expand Down Expand Up @@ -93,19 +113,18 @@ class KubernetesChecks(KubernetesBase, plugintools.PluginPartBase):
plugin_root_index = 8

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
deps = K8S_PACKAGE_DEPS
# Deployments can use snap or apt versions of packages so we check both
self.apt = APTPackageHelper(core_pkgs=K8S_PACKAGES, other_pkgs=deps)
snap_deps = deps + K8S_PACKAGE_DEPS_SNAP
self.snaps = SnapPackageHelper(core_snaps=K8S_PACKAGES,
other_snaps=snap_deps)
self.pebble = PebbleHelper(service_exprs=SERVICES)
self.systemd = SystemdHelper(service_exprs=SERVICES)

@property
def plugin_runnable(self):
if self.apt.core or self.snaps.core:
super().__init__()
KubernetesInstallInfo().mixin(self)

@classmethod
def is_runnable(cls):
"""
Determine whether or not this plugin can and should be run.
@return: True or False
"""
k8s = KubernetesInstallInfo()
if k8s.apt.core or k8s.snaps.core:
return True

return False
32 changes: 21 additions & 11 deletions hotsos/core/plugins/landscape.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,42 @@
from functools import cached_property
from dataclasses import dataclass, field

from hotsos.core.plugintools import PluginPartBase
from hotsos.core.host_helpers import (
APTPackageHelper,
InstallInfoBase,
SystemdHelper,
)

CORE_APT = ['landscape']
SERVICE_EXPRS = [s + '[A-Za-z0-9-]*' for s in CORE_APT]


@dataclass
class LandscapeInstallInfo(InstallInfoBase):
""" Landscape installation information. """
apt: APTPackageHelper = field(default_factory=lambda:
APTPackageHelper(core_pkgs=CORE_APT))
systemd: SystemdHelper = field(default_factory=lambda:
SystemdHelper(service_exprs=SERVICE_EXPRS))


class LandscapeChecks(PluginPartBase):
""" Base class for all Landscape checks. """
plugin_name = 'landscape'
plugin_root_index = 14

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.apt = APTPackageHelper(core_pkgs=CORE_APT)
self.systemd = SystemdHelper(service_exprs=SERVICE_EXPRS)
super().__init__()
LandscapeInstallInfo().mixin(self)

@cached_property
def is_installed(self):
if self.apt.core:
@classmethod
def is_runnable(cls):
"""
Determine whether or not this plugin can and should be run.
@return: True or False
"""
if LandscapeInstallInfo().apt.core:
return True

return False

@property
def plugin_runnable(self):
return self.is_installed
33 changes: 25 additions & 8 deletions hotsos/core/plugins/lxd/common.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from dataclasses import dataclass, field
from functools import cached_property

from hotsos.core.host_helpers import (
APTPackageHelper,
CLIHelperFile,
InstallInfoBase,
SnapPackageHelper,
SystemdHelper,
)
Expand Down Expand Up @@ -41,20 +43,35 @@ def instances(self):
return _instances


@dataclass
class LXDInstallInfo(InstallInfoBase):
""" LXD installation information. """
apt: APTPackageHelper = field(default_factory=lambda:
APTPackageHelper(core_pkgs=CORE_APT))
snaps: SnapPackageHelper = field(default_factory=lambda:
SnapPackageHelper(core_snaps=CORE_SNAPS))
systemd: SystemdHelper = field(default_factory=lambda:
SystemdHelper(service_exprs=SERVICE_EXPRS))


class LXDChecks(PluginPartBase):
""" LXD Checks. """
plugin_name = 'lxd'
plugin_root_index = 11

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.snaps = SnapPackageHelper(core_snaps=CORE_SNAPS)
self.apt = APTPackageHelper(core_pkgs=CORE_APT)
self.systemd = SystemdHelper(service_exprs=SERVICE_EXPRS)

@property
def plugin_runnable(self):
if self.apt.core or self.snaps.core:
super().__init__()
LXDInstallInfo().mixin(self)

@classmethod
def is_runnable(cls):
"""
Determine whether or not this plugin can and should be run.
@return: True or False
"""
lxd_pkgs = LXDInstallInfo()
if lxd_pkgs.apt.core or lxd_pkgs.snaps.core:
return True

return False
Loading

0 comments on commit 7d54d4a

Please sign in to comment.