Skip to content

Commit

Permalink
feature: using COUApplication as base for OpenStackApplication (#242)
Browse files Browse the repository at this point in the history
Like this, we moved usage of libjuju even more to juju_utils + now
OpenStackApplication are frozen static object, which are initialized
without any post initialization steps.

This is based on #240
  • Loading branch information
rgildein committed Feb 15, 2024
1 parent 6710bd8 commit cce6d1c
Show file tree
Hide file tree
Showing 21 changed files with 2,253 additions and 1,846 deletions.
4 changes: 2 additions & 2 deletions cou/apps/auxiliary.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def _get_change_require_osd_release_step(self) -> PreUpgradeStep:
:return: Step to check and set correct value for require-osd-release
:rtype: PreUpgradeStep
"""
ceph_mon_unit, *_ = self.units
ceph_mon_unit, *_ = self.units.values()
return PreUpgradeStep(
description="Ensure require-osd-release option matches with ceph-osd version",
coro=set_require_osd_release_option(ceph_mon_unit.name, self.model),
Expand All @@ -177,7 +177,7 @@ def pre_upgrade_steps(self, target: OpenStackRelease) -> list[PreUpgradeStep]:
:return: List of pre upgrade steps.
:rtype: list[PreUpgradeStep]
"""
for unit in self.units:
for unit in self.units.values():
validate_ovn_support(unit.workload_version)
return super().pre_upgrade_steps(target)

Expand Down
2 changes: 1 addition & 1 deletion cou/apps/auxiliary_subordinate.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,5 @@ def pre_upgrade_steps(self, target: OpenStackRelease) -> list[PreUpgradeStep]:
:return: List of pre upgrade steps.
:rtype: list[PreUpgradeStep]
"""
validate_ovn_support(self.status.workload_version)
validate_ovn_support(self.workload_version)
return super().pre_upgrade_steps(target)
147 changes: 24 additions & 123 deletions cou/apps/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from io import StringIO
from typing import Any, Optional

from juju.client._definitions import ApplicationStatus, UnitStatus
from ruamel.yaml import YAML

from cou.exceptions import (
Expand All @@ -37,7 +36,7 @@
UpgradeStep,
)
from cou.utils.app_utils import upgrade_packages
from cou.utils.juju_utils import COUMachine, COUModel
from cou.utils.juju_utils import COUApplication, COUUnit
from cou.utils.openstack import (
DISTRO_TO_OPENSTACK_MAPPING,
OpenStackCodenameLookup,
Expand All @@ -50,39 +49,9 @@


@dataclass(frozen=True)
class ApplicationUnit:
"""Representation of a single unit of application."""

name: str
os_version: OpenStackRelease
machine: COUMachine
workload_version: str = ""

def __repr__(self) -> str:
"""Representation of the application unit.
:return: Representation of the application unit
:rtype: str
"""
return f"Unit[{self.name}]-Machine[{self.machine.machine_id}]"


@dataclass
class OpenStackApplication:
class OpenStackApplication(COUApplication):
"""Representation of a charmed OpenStack application in the deployment.
:param name: Name of the application
:type name: str
:param status: Status of the application.
:type status: ApplicationStatus
:param config: Configuration of the application.
:type config: dict
:param model: COUModel object
:type model: COUModel
:param charm: Name of the charm.
:type charm: str
:param units: Units representation of an application.
:type units: list[ApplicationUnit]
:raises ApplicationError: When there are no compatible OpenStack release for the
workload version.
:raises MismatchedOpenStackVersions: When units part of this application are running mismatched
Expand All @@ -92,31 +61,21 @@ class OpenStackApplication:
:raises RunUpgradeError: When an upgrade fails.
"""

# pylint: disable=too-many-instance-attributes

name: str
status: ApplicationStatus
config: dict
model: COUModel
charm: str
machines: dict[str, COUMachine]
units: list[ApplicationUnit] = field(default_factory=lambda: [])
packages_to_hold: Optional[list] = field(default=None, init=False)
wait_timeout: int = field(default=DEFAULT_WAITING_TIMEOUT, init=False)
wait_for_model: bool = field(default=False, init=False) # waiting only for application itself

def __post_init__(self) -> None:
"""Initialize the Application dataclass."""
self._verify_channel()
self._populate_units()

def __hash__(self) -> int:
"""Hash magic method for Application.
:return: Unique hash identifier for Application object.
:rtype: int
"""
return hash(f"{self.name}{self.charm}")
return hash(f"{self.name}({self.charm})")

def __eq__(self, other: Any) -> bool:
"""Equal magic method for Application.
Expand All @@ -138,15 +97,15 @@ def __str__(self) -> str:
self.name: {
"model_name": self.model.name,
"charm": self.charm,
"charm_origin": self.charm_origin,
"charm_origin": self.origin,
"os_origin": self.os_origin,
"channel": self.channel,
"units": {
unit.name: {
"workload_version": unit.workload_version,
"os_version": str(unit.os_version),
"os_version": str(self._get_latest_os_version(unit)),
}
for unit in self.units
for unit in self.units.values()
},
}
}
Expand All @@ -160,59 +119,18 @@ def _verify_channel(self) -> None:
:raises ApplicationError: Exception raised when channel is not a valid OpenStack channel.
"""
if self.is_from_charm_store or self.is_valid_track(self.status.charm_channel):
logger.debug("%s app has proper channel %s", self.name, self.status.charm_channel)
if self.is_from_charm_store or self.is_valid_track(self.channel):
logger.debug("%s app has proper channel %s", self.name, self.channel)
return

raise ApplicationError(
f"Channel: {self.status.charm_channel} for charm '{self.charm}' on series "
f"Channel: {self.channel} for charm '{self.charm}' on series "
f"'{self.series}' is currently not supported in this tool. Please take a look at the "
"documentation: "
"https://docs.openstack.org/charm-guide/latest/project/charm-delivery.html to see if "
"you are using the right track."
)

def _populate_units(self) -> None:
"""Populate application units."""
if not self.is_subordinate:
for name, unit in self.status.units.items():
compatible_os_version = self._get_latest_os_version(unit)
self.units.append(
ApplicationUnit(
name=name,
workload_version=unit.workload_version,
os_version=compatible_os_version,
machine=self.machines[unit.machine],
)
)

@property
def is_subordinate(self) -> bool:
"""Check if application is subordinate.
:return: True if subordinate, False otherwise.
:rtype: bool
"""
return bool(self.status.subordinate_to)

@property
def channel(self) -> str:
"""Get charm channel of the application.
:return: Charm channel. E.g: ussuri/stable
:rtype: str
"""
return self.status.charm_channel

@property
def charm_origin(self) -> str:
"""Get the charm origin of application.
:return: Charm origin. E.g: cs or ch
:rtype: str
"""
return self.status.charm.split(":")[0]

@property
def os_origin(self) -> str:
"""Get application configuration for openstack-origin or source.
Expand Down Expand Up @@ -240,15 +158,6 @@ def origin_setting(self) -> Optional[str]:

return None

@property
def is_from_charm_store(self) -> bool:
"""Check if application comes from charm store.
:return: True if comes, False otherwise.
:rtype: bool
"""
return self.charm_origin == "cs"

def is_valid_track(self, charm_channel: str) -> bool:
"""Check if the channel track is valid.
Expand All @@ -263,11 +172,11 @@ def is_valid_track(self, charm_channel: str) -> bool:
except ValueError:
return self.is_from_charm_store

def _get_latest_os_version(self, unit: UnitStatus) -> OpenStackRelease:
def _get_latest_os_version(self, unit: COUUnit) -> OpenStackRelease:
"""Get the latest compatible OpenStack release based on the unit workload version.
:param unit: Application Unit
:type unit: UnitStatus
:type unit: COUUnit
:raises ApplicationError: When there are no compatible OpenStack release for the
workload version.
:return: The latest compatible OpenStack release.
Expand Down Expand Up @@ -314,15 +223,6 @@ def target_channel(self, target: OpenStackRelease) -> str:
"""
return f"{target.codename}/stable"

@property
def series(self) -> str:
"""Ubuntu series of the application.
:return: Ubuntu series of application. E.g: focal
:rtype: str
"""
return self.status.series

@property
def current_os_release(self) -> OpenStackRelease:
"""Current OpenStack Release of the application.
Expand All @@ -333,8 +233,9 @@ def current_os_release(self) -> OpenStackRelease:
:rtype: OpenStackRelease
"""
os_versions = defaultdict(list)
for unit in self.units:
os_versions[unit.os_version].append(unit.name)
for unit in self.units.values():
os_version = self._get_latest_os_version(unit)
os_versions[os_version].append(unit.name)

if len(os_versions.keys()) == 1:
return next(iter(os_versions))
Expand Down Expand Up @@ -406,7 +307,7 @@ def can_upgrade_current_channel(self) -> bool:
:return: True if can upgrade, False otherwise.
:rtype: bool
"""
return bool(self.status.can_upgrade_to)
return bool(self.can_upgrade_to)

def new_origin(self, target: OpenStackRelease) -> str:
"""Return the new openstack-origin or source configuration.
Expand Down Expand Up @@ -529,8 +430,8 @@ def _get_upgrade_current_release_packages_step(self) -> PreUpgradeStep:
for unit in self.units:
step.add_step(
UnitUpgradeStep(
description=f"Upgrade software packages on unit {unit.name}",
coro=upgrade_packages(unit.name, self.model, self.packages_to_hold),
description=f"Upgrade software packages on unit {unit}",
coro=upgrade_packages(unit, self.model, self.packages_to_hold),
)
)

Expand Down Expand Up @@ -565,7 +466,7 @@ def _get_refresh_charm_step(self, target: OpenStackRelease) -> PreUpgradeStep:
channel,
)

if self.charm_origin == "cs":
if self.origin == "cs":
description = f"Migration of '{self.name}' from charmstore to charmhub"
switch = f"ch:{self.charm}"
elif self.channel in self.possible_current_channels:
Expand Down Expand Up @@ -643,11 +544,11 @@ def _get_enable_action_managed_step(self) -> UpgradeStep:
coro=self.model.set_application_config(self.name, {"action-managed-upgrade": True}),
)

def _get_pause_unit_step(self, unit: ApplicationUnit) -> UnitUpgradeStep:
def _get_pause_unit_step(self, unit: COUUnit) -> UnitUpgradeStep:
"""Get the step to pause a unit to upgrade.
:param unit: Unit to be paused.
:type unit: ApplicationUnit
:type unit: COUUnit
:return: Step to pause a unit.
:rtype: UnitUpgradeStep
"""
Expand All @@ -658,11 +559,11 @@ def _get_pause_unit_step(self, unit: ApplicationUnit) -> UnitUpgradeStep:
),
)

def _get_resume_unit_step(self, unit: ApplicationUnit) -> UnitUpgradeStep:
def _get_resume_unit_step(self, unit: COUUnit) -> UnitUpgradeStep:
"""Get the step to resume a unit after upgrading the workload version.
:param unit: Unit to be resumed.
:type unit: ApplicationUnit
:type unit: COUUnit
:return: Step to resume a unit.
:rtype: UnitUpgradeStep
"""
Expand All @@ -673,11 +574,11 @@ def _get_resume_unit_step(self, unit: ApplicationUnit) -> UnitUpgradeStep:
),
)

def _get_openstack_upgrade_step(self, unit: ApplicationUnit) -> UnitUpgradeStep:
def _get_openstack_upgrade_step(self, unit: COUUnit) -> UnitUpgradeStep:
"""Get the step to upgrade a unit.
:param unit: Unit to be upgraded.
:type unit: ApplicationUnit
:type unit: COUUnit
:return: Step to upgrade a unit.
:rtype: UnitUpgradeStep
"""
Expand Down
11 changes: 5 additions & 6 deletions cou/apps/channel_based.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@
"""Channel based application class."""
import logging

from juju.client._definitions import UnitStatus

from cou.apps.base import OpenStackApplication
from cou.apps.factory import AppFactory
from cou.steps import PostUpgradeStep
from cou.utils.juju_utils import COUUnit
from cou.utils.openstack import CHANNEL_BASED_CHARMS, OpenStackRelease

logger = logging.getLogger(__name__)
Expand All @@ -28,11 +27,11 @@
class OpenStackChannelBasedApplication(OpenStackApplication):
"""Application for charms that are channel based."""

def _get_latest_os_version(self, unit: UnitStatus) -> OpenStackRelease:
def _get_latest_os_version(self, unit: COUUnit) -> OpenStackRelease:
"""Get the latest compatible OpenStack release based on the channel.
:param unit: Application Unit
:type unit: UnitStatus
:param unit: COUUnit
:type unit: COUUnit
:raises ApplicationError: When there are no compatible OpenStack release for the
workload version.
:return: The latest compatible OpenStack release.
Expand All @@ -58,7 +57,7 @@ def is_versionless(self) -> bool:
:return: True if is versionless, False otherwise.
:rtype: bool
"""
return not all(unit.workload_version for unit in self.status.units.values())
return not all(unit.workload_version for unit in self.units.values())

def post_upgrade_steps(self, target: OpenStackRelease) -> list[PostUpgradeStep]:
"""Post Upgrade steps planning.
Expand Down
Loading

0 comments on commit cce6d1c

Please sign in to comment.