Skip to content
Merged
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
4 changes: 4 additions & 0 deletions packaging/leapp-repository-changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
* Tue Aug 11 2026 Roman Prilipskii <rprilipskii@cloudlinux.com> 0.20.0-11.cloudlinux
- CLOS-6809: Fix the upgrade being blocked on servers running MySQL Governor with MariaDB 11.4 or 11.8, where the installed database version was reported in a form that the cl-mysql repository check did not recognise
- Determine the database module to enable from the configured cl-mysql repository, so that newly released CloudLinux MySQL and MariaDB versions are upgraded correctly without waiting for a Leapp update

* Thu Aug 06 2026 Roman Prilipskii <rprilipskii@cloudlinux.com> 0.20.0-10.cloudlinux
- CLOS-2132: Warn before the upgrade when the PostgreSQL configuration would keep the database from starting on the new system, and spell out the migration steps to run afterwards
- CLOS-3716: Stop the upgrade when the target repositories offer a kernel newer than the rest of the target system, which could leave LVE unusable after the reboot
Expand Down
2 changes: 1 addition & 1 deletion packaging/leapp-repository.spec
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ py2_byte_compile "%1" "%2"}

Name: leapp-repository
Version: 0.20.0
Release: 10%{?dist}.cloudlinux
Release: 11%{?dist}.cloudlinux
Summary: Repositories for leapp

License: ASL 2.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
ClMysqlTypeStatus,
construct_repomap_data,
get_clmysql_type,
get_expected_repo_url_fragment,
parse_clmysql_repo_url,
parse_clmysql_type,
)
from leapp.libraries.common.config.version import get_target_major_version
from leapp.libraries.stdlib import api
Expand Down Expand Up @@ -82,64 +83,99 @@ def clmysql_process(lib, repofile_name, repofile_data):

api.current_logger().debug("repoids from CloudLinux repofile {}: {}".format(repofile_name, data_to_log))

# Validate that cl-mysql-meta repo's baseurl matches the detected DB type.
# Governor configures the URL based on the selected DB version (e.g. cl-mariadb-10.6 for mariadb106).
# If the user switched DB versions without re-running Governor --install,
# the repo may point to the wrong package set.
# Validate that the cl-mysql-meta repo's baseurl matches the detected DB type.
# Governor picks the URL from the selected DB version (e.g. cl-mariadb-10.6 for
# mariadb106). If the user switched DB versions without re-running Governor
# --install, the repo may point at the wrong package set.
#
# Compare the parsed versions rather than a predicted directory name. Governor's
# spelling is not derivable - 11.4 is published as cl-mariadb-11.04 but 10.6 as
# cl-mariadb-10.6 - and predicting it produced a phantom cl-mariadb-11.4, which
# inhibited every MariaDB 11.x upgrade and printed a 404 URL as the remedy
# (CLOS-6809).
#
# We inhibit rather than auto-correct because Governor is the authoritative source
# for this repo file and has the real download-and-write logic;
# re-running --install is the proper fix.
expected_fragment = get_expected_repo_url_fragment(lib.clmysql_type)
if expected_fragment:
detected_version = parse_clmysql_type(lib.clmysql_type)
if detected_version:
for repo_data in repofile_data.data:
if repo_data.repoid == "cl-mysql-meta" and "/{}/".format(expected_fragment) not in repo_data.baseurl:
if repo_data.repoid != "cl-mysql-meta":
continue
repo_version = parse_clmysql_repo_url(repo_data.baseurl)
if repo_version == detected_version:
# Confirmed to describe the installed DB, so it is a trustworthy source
# for the DNF module stream, which the type token cannot spell reliably.
lib.clmysql_meta_baseurl = repo_data.baseurl
continue
# Either the repository describes a different version, or its URL cannot be
# read at all. Both mean the repository cannot be confirmed to match the
# installed database, and an unconfirmable repository is exactly what this
# check exists to stop - letting it through would hand the upgrade a
# repository nobody has validated.
if repo_version is None:
api.current_logger().warning(
"cl-mysql-meta repo baseurl '{}' does not name a recognisable database "
"version; cannot confirm it matches the installed type '{}'."
.format(repo_data.baseurl, lib.clmysql_type)
)
detail = (
"the cl-mysql-meta repository URL does not name a database version that "
"Leapp can read: '{0}'. Leapp cannot confirm that this repository provides "
"the installed database, and will not upgrade against a repository it "
"cannot verify.".format(repo_data.baseurl)
)
else:
api.current_logger().warning(
"cl-mysql-meta repo baseurl '{}' does not match detected DB type '{}' "
"(expected '{}' in URL)."
.format(repo_data.baseurl, lib.clmysql_type, expected_fragment)
"(repo describes {}.{}, installed is {}.{})."
.format(
repo_data.baseurl, lib.clmysql_type,
repo_version[1], repo_version[2],
detected_version[1], detected_version[2],
)
)
reporting.create_report(
[
reporting.Title(
"cl-mysql.repo does not match the installed database type"
),
reporting.Summary(
"The cl-mysql-meta repository is configured for a different "
"database type than what is actually installed. "
"The detected database type is '{}', but the cl-mysql-meta "
"repo URL points to '{}'. "
"This may happen when the database version was changed "
"without a follow-up '/usr/share/lve/dbgovernor/mysqlgovernor.py --install', or the "
"cl-mysql.repo file was manually edited. "
"Proceeding with the wrong repository would result in "
"an incorrect upgrade operation."
.format(lib.clmysql_type, repo_data.baseurl)
),
reporting.Severity(reporting.Severity.HIGH),
reporting.Groups(
[
reporting.Groups.REPOSITORY,
reporting.Groups.OS_FACTS
]
),
reporting.Groups([reporting.Groups.INHIBITOR]),
reporting.Remediation(
hint=(
"Download the correct repository file for the installed "
"database type: "
"curl -o /etc/yum.repos.d/cl-mysql.repo "
"http://repo.cloudlinux.com/other/"
"cl${{releasever}}/mysqlmeta/{expected}-common.repo\n"
"Or re-run MySQL Governor to regenerate it "
"(this reinstalls the full DB stack): "
"/usr/share/lve/dbgovernor/mysqlgovernor.py --install --yes\n"
"Then restart the upgrade process."
.format(expected=expected_fragment)
)
),
]
detail = (
"the cl-mysql-meta repository is configured for {0} {1}.{2} while the "
"installed database is {3} {4}.{5}: '{6}'.".format(
repo_version[0], repo_version[1], repo_version[2],
detected_version[0], detected_version[1], detected_version[2],
repo_data.baseurl,
)
)
return
reporting.create_report(
[
reporting.Title(
"cl-mysql.repo does not match the installed database type"
),
reporting.Summary(
"The database packages that the upgrade installs come from the "
"cl-mysql-meta repository, so it has to describe the database that is "
"actually installed. Here {0} "
"This may happen when the database version was changed without a "
"follow-up '/usr/share/lve/dbgovernor/mysqlgovernor.py --install', or "
"when the cl-mysql.repo file was edited by hand. Proceeding would "
"result in an incorrect upgrade operation.".format(detail)
),
reporting.Severity(reporting.Severity.HIGH),
reporting.Groups(
[
reporting.Groups.REPOSITORY,
reporting.Groups.OS_FACTS
]
),
reporting.Groups([reporting.Groups.INHIBITOR]),
reporting.Remediation(
hint=(
"Re-run MySQL Governor to regenerate the repository file for the "
"installed database (this reinstalls the full DB stack): "
"/usr/share/lve/dbgovernor/mysqlgovernor.py --install --yes\n"
"Then restart the upgrade process."
)
),
]
)
return

cl_target_repofile_list = []
target_major = get_target_major_version()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
)
from leapp.libraries.common.clmysql import (
MODULE_STREAMS,
canonical_clmysql_type,
clmysql_module_stream_from_url,
construct_repomap_data,
get_pkg_prefix,
resolve_clmysql_module_stream,
Expand Down Expand Up @@ -67,6 +69,8 @@ class MySqlRepositorySetupLibrary(object):
def __init__(self):
self.mysql_types = set()
self.clmysql_type = None
# baseurl of the cl-mysql-meta repo, once confirmed to match the installed DB.
self.clmysql_meta_baseurl = None
# Messages to send about custom generated package repositories.
self.custom_repo_msgs = []
self.mapping_msgs = []
Expand Down Expand Up @@ -116,23 +120,36 @@ def finalize(self):
)

if "cloudlinux" in self.mysql_types and self.clmysql_type:
mod_name, mod_stream = resolve_clmysql_module_stream(self.clmysql_type)
mod_name, mod_stream = resolve_clmysql_module_stream(
self.clmysql_type, baseurl=self.clmysql_meta_baseurl
)
if mod_name and mod_stream:
if self.clmysql_type not in MODULE_STREAMS:
# Ask where the stream actually came from. Governor caches a spelling that
# MODULE_STREAMS does not list ("mariadb114" for MariaDB 11.4), so testing
# the raw token would report a verified stream as though it had been guessed.
if canonical_clmysql_type(self.clmysql_type) in MODULE_STREAMS:
stream_source = None
elif clmysql_module_stream_from_url(self.clmysql_meta_baseurl)[1] == mod_stream:
stream_source = "the configured cl-mysql repository"
else:
stream_source = "the detected database type"

if stream_source:
api.current_logger().warning(
"CL database type {} is not in MODULE_STREAMS; using derived DNF module {}:{}. "
"Add an explicit MODULE_STREAMS entry when this stream is product-supported."
.format(self.clmysql_type, mod_name, mod_stream)
"CL database type {} is not in MODULE_STREAMS; using DNF module {}:{} derived "
"from {}. Add an explicit MODULE_STREAMS entry when this stream is "
"product-supported."
.format(self.clmysql_type, mod_name, mod_stream, stream_source)
)
reporting.create_report(
[
reporting.Title("CloudLinux database module stream was derived automatically"),
reporting.Summary(
"The active CloudLinux MySQL/MariaDB/Percona type ({0}) has no explicit Leapp "
"MODULE_STREAMS entry. Leapp will enable DNF module {1}:{2} derived from the "
"detected type string. If the upgrade fails, confirm this module exists for the "
"target OS and add MODULE_STREAMS in Leapp if the product stream name differs."
.format(self.clmysql_type, mod_name, mod_stream)
"MODULE_STREAMS entry. Leapp will enable DNF module {1}:{2}, derived from "
"{3}. If the upgrade fails, confirm this module exists for the target OS and "
"add MODULE_STREAMS in Leapp if the product stream name differs."
.format(self.clmysql_type, mod_name, mod_stream, stream_source)
),
reporting.Severity(reporting.Severity.MEDIUM),
reporting.Groups([reporting.Groups.REPOSITORY, reporting.Groups.OS_FACTS]),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""
CLOS-6809 regression: a Governor-managed MariaDB 11.4 (or 11.8) system was
inhibited even though nothing on it was wrong.

Governor derives the token it caches in mysql.type.installed by concatenating the
RPM major and minor version, so an 11.4.12 install is recorded as 'mariadb114'.
The canonical Governor token is 'mariadb1104' and the published repository
directory is cl-mariadb-11.04, so predicting the URL fragment from the cached
token produced 'cl-mariadb-11.4' - a path that does not exist - and the handler
inhibited the upgrade with a remediation hint pointing at a 404.

The handler now compares parsed versions instead of predicting the spelling, so
either spelling of the token agrees with either spelling of the URL. These tests
drive it with the cached token exactly as it appears on disk, against the
repofile Governor actually writes.
"""

from leapp import reporting
from leapp.libraries.actor.clmysql_cloudlinux import clmysql_process
from leapp.libraries.actor.clmysqlrepositorysetup import MySqlRepositorySetupLibrary

# Verbatim from http://repo.cloudlinux.com/other/cl8/mysqlmeta/cl-mariadb-11.04-common.repo
_BASEURL_1104 = "http://repo.cloudlinux.com/other/cl$releasever/mysqlmeta/cl-mariadb-11.04/$basearch/"
_BASEURL_1108 = "http://repo.cloudlinux.com/other/cl$releasever/mysqlmeta/cl-mariadb-11.08/$basearch/"
# A URL whose version component Leapp cannot read at all.
_BASEURL_UNREADABLE = "http://repo.cloudlinux.com/other/cl$releasever/mysqlmeta/current/$basearch/"


class TestGovernorTokenSpelling:

def test_mariadb_11_4_is_not_inhibited(self, patch_env, make_cl_mysql_repofile):
patch_env(clmysql_type="mariadb114")

lib = MySqlRepositorySetupLibrary()
clmysql_process(
lib,
"cl-mysql",
make_cl_mysql_repofile(cl_mysql_meta_baseurl=_BASEURL_1104),
)

assert "cloudlinux" in lib.mysql_types
assert lib.custom_repo_msgs != []
assert reporting.create_report.called == 0

def test_mariadb_11_8_is_not_inhibited(self, patch_env, make_cl_mysql_repofile):
patch_env(clmysql_type="mariadb118")

lib = MySqlRepositorySetupLibrary()
clmysql_process(
lib,
"cl-mysql",
make_cl_mysql_repofile(cl_mysql_meta_baseurl=_BASEURL_1108),
)

assert "cloudlinux" in lib.mysql_types
assert lib.custom_repo_msgs != []
assert reporting.create_report.called == 0

def test_canonical_token_against_padded_url_is_not_inhibited(self, patch_env, make_cl_mysql_repofile):
"""The other spelling pairing: canonical token, padded URL."""
patch_env(clmysql_type="mariadb1104")

lib = MySqlRepositorySetupLibrary()
clmysql_process(
lib,
"cl-mysql",
make_cl_mysql_repofile(cl_mysql_meta_baseurl=_BASEURL_1104),
)

assert "cloudlinux" in lib.mysql_types
assert reporting.create_report.called == 0

def test_genuinely_wrong_repofile_still_inhibits(self, patch_env, make_cl_mysql_repofile):
"""An 11.4 install pointed at the 10.6 repository is still a real problem."""
patch_env(clmysql_type="mariadb114")

lib = MySqlRepositorySetupLibrary()
clmysql_process(lib, "cl-mysql", make_cl_mysql_repofile())

assert "cloudlinux" not in lib.mysql_types
assert lib.custom_repo_msgs == []
assert reporting.create_report.called == 1

def test_remediation_does_not_point_at_a_guessed_url(self, patch_env, make_cl_mysql_repofile):
"""
The old hint told customers to curl a predicted -common.repo file, which 404s
for MariaDB 11.x. Re-running Governor regenerates the file by definition.
"""
patch_env(clmysql_type="mariadb114")

lib = MySqlRepositorySetupLibrary()
clmysql_process(lib, "cl-mysql", make_cl_mysql_repofile())

hint = reporting.create_report.report_fields["detail"]["remediations"][0]["context"]
assert "-common.repo" not in hint
assert "mysqlgovernor.py --install --yes" in hint


class TestUnreadableRepoUrl:
"""
A cl-mysql-meta URL whose version cannot be read must inhibit. Comparing parsed
versions replaced a substring test that inhibited on anything unrecognised, and
for a while an unreadable URL slipped through: the repository was copied,
force-enabled and handed to the upgrade without ever being checked.
"""

def test_unreadable_url_inhibits(self, patch_env, make_cl_mysql_repofile):
patch_env(clmysql_type="mariadb114")

lib = MySqlRepositorySetupLibrary()
clmysql_process(
lib,
"cl-mysql",
make_cl_mysql_repofile(cl_mysql_meta_baseurl=_BASEURL_UNREADABLE),
)

assert reporting.create_report.called == 1
assert "cloudlinux" not in lib.mysql_types
assert lib.custom_repo_msgs == []

def test_unreadable_url_says_why(self, patch_env, make_cl_mysql_repofile):
patch_env(clmysql_type="mariadb114")

lib = MySqlRepositorySetupLibrary()
clmysql_process(
lib,
"cl-mysql",
make_cl_mysql_repofile(cl_mysql_meta_baseurl=_BASEURL_UNREADABLE),
)

summary = reporting.create_report.report_fields["summary"]
assert "does not name a database version" in summary
assert _BASEURL_UNREADABLE in summary
Loading
Loading