Skip to content

Commit

Permalink
Add support for latest/stable (#347)
Browse files Browse the repository at this point in the history
- currently if the applications uses `latest/stable` channel it's not
considered as a valid channel to upgrade.
- the approach is to move to an OpenStack release channel when this
happens, based on the current workload version
  • Loading branch information
gabrielcocenza authored Apr 5, 2024
1 parent ce04949 commit 4d6fa9f
Show file tree
Hide file tree
Showing 11 changed files with 539 additions and 201 deletions.
53 changes: 33 additions & 20 deletions cou/apps/auxiliary.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,31 +53,31 @@ def is_valid_track(self, charm_channel: str) -> bool:
logger.debug("'%s' has been installed from the charm store", self.name)
return True

track = self._get_track_from_channel(charm_channel)
return (self.charm, self.series, track) in TRACK_TO_OPENSTACK_MAPPING
current_track = self._get_track_from_channel(charm_channel)
possible_tracks = OPENSTACK_TO_TRACK_MAPPING.get(
(self.charm, self.series, self.current_os_release.codename), []
)
return (
self.charm,
self.series,
current_track,
) in TRACK_TO_OPENSTACK_MAPPING and len(possible_tracks) > 0

@property
def possible_current_channels(self) -> list[str]:
"""Return the possible current channels based on the series and current OpenStack release.
def expected_current_channel(self) -> str:
"""Return the expected current channel.
:return: The possible current channels for the application.
:rtype: list[str]
:raises ApplicationError: When cannot find tracks.
Expected current channel is the channel that the application is suppose to be using based
on the current series, workload version and, by consequence, the OpenStack release
identified.
:return: The expected current channel of the application. E.g: "3.9/stable"
:rtype: str
"""
tracks = OPENSTACK_TO_TRACK_MAPPING.get(
*_, track = OPENSTACK_TO_TRACK_MAPPING[
(self.charm, self.series, self.current_os_release.codename)
)
if tracks:
return [f"{track}/stable" for track in tracks]
]

raise ApplicationError(
(
f"Cannot find a suitable '{self.charm}' charm channel for "
f"{self.current_os_release.codename} on series '{self.series}'. "
"Please take a look at the documentation: "
"https://docs.openstack.org/charm-guide/latest/project/charm-delivery.html"
)
)
return f"{track}/stable"

def target_channel(self, target: OpenStackRelease) -> str:
"""Return the appropriate channel for the passed OpenStack target.
Expand Down Expand Up @@ -124,7 +124,6 @@ def channel_codename(self) -> OpenStackRelease:

track: str = self._get_track_from_channel(self.channel)
compatible_os_releases = TRACK_TO_OPENSTACK_MAPPING[(self.charm, self.series, track)]
# channel setter already validate if it is a valid channel.
return max(compatible_os_releases)

def generate_upgrade_plan(
Expand Down Expand Up @@ -154,6 +153,20 @@ def generate_upgrade_plan(
)
return super().generate_upgrade_plan(target, force, None)

def _need_current_channel_refresh(self, target: OpenStackRelease) -> bool:
"""Check if the application needs to refresh the current channel.
:param target: OpenStack release as target to upgrade.
:type target: OpenStackRelease
:return: True if needs to refresh, False otherwise
:rtype: bool
"""
track: str = self._get_track_from_channel(self.channel)
compatible_os_releases = TRACK_TO_OPENSTACK_MAPPING[(self.charm, self.series, track)]
return bool(self.can_upgrade_to) and any(
os_release <= target for os_release in compatible_os_releases
)


@AppFactory.register_application(["rabbitmq-server"])
class RabbitMQServer(AuxiliaryApplication):
Expand Down
123 changes: 77 additions & 46 deletions cou/apps/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
LONG_IDLE_TIMEOUT: int = int(os.environ.get("COU_LONG_IDLE_TIMEOUT", 30 * 60)) # default of 30 min
ORIGIN_SETTINGS = ("openstack-origin", "source")
REQUIRED_SETTINGS = ("enable-auto-restarts", "action-managed-upgrade", *ORIGIN_SETTINGS)
LATEST_STABLE = "latest/stable"


@dataclass(frozen=True)
Expand Down Expand Up @@ -139,7 +140,11 @@ 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.channel):
if (
self.is_from_charm_store
or self.channel == LATEST_STABLE
or self.is_valid_track(self.channel)
):
logger.debug("%s app has proper channel %s", self.name, self.channel)
return

Expand Down Expand Up @@ -236,13 +241,17 @@ def origin_setting(self) -> Optional[str]:
return None

@property
def possible_current_channels(self) -> list[str]:
"""Return the possible current channels based on the current OpenStack release.
def expected_current_channel(self) -> str:
"""Return the expected current channel.
Expected current channel is the channel that the application is suppose to be using based
on the current series, workload version and, by consequence, the OpenStack release
identified.
:return: The possible current channels for the application. E.g: ["ussuri/stable"]
:rtype: list[str]
:return: The expected current channel of the application. E.g: "ussuri/stable"
:rtype: str
"""
return [f"{self.current_os_release.codename}/stable"]
return f"{self.current_os_release.codename}/stable"

@property
def os_release_units(self) -> dict[OpenStackRelease, list[str]]:
Expand All @@ -263,7 +272,7 @@ def is_valid_track(self, charm_channel: str) -> bool:
:param charm_channel: Charm channel. E.g: ussuri/stable
:type charm_channel: str
:return: True if valid, False otherwise.
:return: True if valid, False otherwise
:rtype: bool
"""
try:
Expand Down Expand Up @@ -519,57 +528,79 @@ def _get_upgrade_current_release_packages_step(
return step

def _get_refresh_charm_step(self, target: OpenStackRelease) -> PreUpgradeStep:
"""Get step for refreshing the current channel.
"""Get step for refreshing the charm.
This function also identifies if charm comes from charmstore and in that case,
makes the migration.
:param target: OpenStack release as target to upgrade.
:param target: OpenStack release as target to upgrade
:type target: OpenStackRelease
:raises ApplicationError: When application has unexpected channel.
:return: Step for refreshing the charm.
:return: Step for refreshing the charm
:rtype: PreUpgradeStep
"""
if not self.can_upgrade_to:
return PreUpgradeStep()
if self.is_from_charm_store:
return self._get_charmhub_migration_step()
if self.channel == LATEST_STABLE:
return self._get_change_to_openstack_channels_step()
if self._need_current_channel_refresh(target):
return self._get_refresh_current_channel_step()
logger.info(
"'%s' does not need to refresh the current channel: %s", self.name, self.channel
)
return PreUpgradeStep()

switch = None
*_, channel = self.possible_current_channels
def _get_charmhub_migration_step(self) -> PreUpgradeStep:
"""Get the step for charm hub migration from charm store.
# corner case for rabbitmq and hacluster.
if len(self.possible_current_channels) > 1:
logger.info(
"'%s' has more than one channel compatible with the current OpenStack release: "
"'%s'. '%s' will be used",
self.name,
self.current_os_release.codename,
channel,
)
:return: Step for charmhub migration
:rtype: PreUpgradeStep
"""
return PreUpgradeStep(
f"Migrate '{self.name}' from charmstore to charmhub",
coro=self.model.upgrade_charm(
self.name, self.expected_current_channel, switch=f"ch:{self.charm}"
),
)

if self.origin == "cs":
description = f"Migrate '{self.name}' from charmstore to charmhub"
switch = f"ch:{self.charm}"
elif self.channel in self.possible_current_channels:
channel = self.channel
description = f"Refresh '{self.name}' to the latest revision of '{channel}'"
elif self.channel_codename >= target:
logger.info(
"Skipping charm refresh for %s, its channel is already set to %s.",
self.name,
self.channel,
)
return PreUpgradeStep()
elif self.channel not in self.possible_current_channels:
raise ApplicationError(
f"'{self.name}' has unexpected channel: '{self.channel}' for the current workload "
f"version and OpenStack release: '{self.current_os_release.codename}'. "
f"Possible channels are: {','.join(self.possible_current_channels)}"
)
def _get_change_to_openstack_channels_step(self) -> PreUpgradeStep:
"""Get the step for changing to OpenStack channels.
:return: Step for changing to OpenStack channels
:rtype: PreUpgradeStep
"""
logger.warning(
"Changing '%s' channel from %s to %s. This may be a charm downgrade, "
"which is generally not supported.",
self.name,
self.channel,
self.expected_current_channel,
)
return PreUpgradeStep(
description=description,
coro=self.model.upgrade_charm(self.name, channel, switch=switch),
f"WARNING: Changing '{self.name}' channel from {self.channel} to "
f"{self.expected_current_channel}. This may be a charm downgrade, "
"which is generally not supported.",
coro=self.model.upgrade_charm(self.name, self.expected_current_channel),
)

def _get_refresh_current_channel_step(self) -> PreUpgradeStep:
"""Get step for refreshing the current channel.
:return: Step for refreshing the charm
:rtype: PreUpgradeStep
"""
return PreUpgradeStep(
f"Refresh '{self.name}' to the latest revision of '{self.channel}'",
coro=self.model.upgrade_charm(self.name, self.channel),
)

def _need_current_channel_refresh(self, target: OpenStackRelease) -> bool:
"""Check if the application needs to refresh the current channel.
:param target: OpenStack release as target to upgrade.
:type target: OpenStackRelease
:return: True if needs to refresh, False otherwise
:rtype: bool
"""
return bool(self.can_upgrade_to) and self.channel_codename <= target

def _get_upgrade_charm_step(self, target: OpenStackRelease) -> UpgradeStep:
"""Get step for upgrading the charm.
Expand Down
16 changes: 7 additions & 9 deletions cou/apps/subordinate.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,17 @@ def _check_application_target(self, target: OpenStackRelease) -> None:
f"than {target}. Ignoring."
)

def pre_upgrade_steps(
self, target: OpenStackRelease, units: Optional[list[Unit]]
) -> list[PreUpgradeStep]:
"""Pre Upgrade steps planning.
def _get_upgrade_current_release_packages_step(
self, units: Optional[list[Unit]]
) -> PreUpgradeStep:
"""Get step for upgrading software packages to the latest of the current release.
:param target: OpenStack release as target to upgrade.
:type target: OpenStackRelease
:param units: Units to generate upgrade plan
:type units: Optional[list[Unit]]
:return: List of pre upgrade steps.
:rtype: list[PreUpgradeStep]
:return: Step for upgrading software packages to the latest of the current release.
:rtype: PreUpgradeStep
"""
return [self._get_refresh_charm_step(target)]
return PreUpgradeStep()

def upgrade_steps(
self, target: OpenStackRelease, units: Optional[list[Unit]], force: bool
Expand Down
7 changes: 4 additions & 3 deletions tests/mocked_plans/sample_plans/base.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ plan: |
Verify that all 'nova-compute' units has been upgraded
Upgrade software packages of 'ceph-osd' from the current APT repositories
Upgrade software packages on unit 'ceph-osd/0'
Refresh 'ceph-osd' to the latest revision of 'octopus/stable'
Change charm config of 'ceph-osd' 'source' to 'cloud:focal-victoria'
Wait for up to 300s for app 'ceph-osd' to reach the idle state
Verify that the workload of 'ceph-osd' has been upgraded on units: ceph-osd/0
Expand Down Expand Up @@ -99,13 +100,13 @@ applications:
origin: ch
series: focal
subordinate_to: []
workload_version: 17.0.1
workload_version: 15.2.0
units:
ceph-osd/0:
name: ceph-osd/0
machine: '2'
workload_version: 17.0.1
os_version: xena
workload_version: 15.2.0
os_version: victoria
machines:
'2':
id: '2'
Expand Down
Loading

0 comments on commit 4d6fa9f

Please sign in to comment.