koji_block_retired: Do not block on stable branches

Signed-off-by: Lenka Segura <lsegura@redhat.com>
This commit is contained in:
Lenka Segura 2024-11-29 16:57:53 +01:00
parent 901d77f0b5
commit dba8da1dc6
4 changed files with 315 additions and 8 deletions

View file

@ -208,6 +208,49 @@ class TestProcessBlockRetired:
== "Cannot block package example-repo on frozen branch main, bailing."
)
@patch("toddlers.plugins.koji_block_retired.bodhi.set_bodhi")
@patch("toddlers.plugins.koji_block_retired.pagure.set_pagure")
@patch("toddlers.plugins.koji_block_retired.KojiBlockRetired._create_session")
@patch("toddlers.plugins.koji_block_retired.KojiBlockRetired.get_tag_from_target")
def test_dead_package_added_to_stable_branch(
self,
mock_get_tag_from_target,
mock_session,
mock_pagure,
mock_set_bodhi,
config,
caplog,
):
"""
Assert that that main branch will be changed to rawhide tag
"""
mock_get_tag_from_target.return_value = "f41"
caplog.set_level(logging.INFO)
mock_dist_git = MagicMock()
mock_dist_git.has_dead_package_on_branch.return_value = True
mock_pagure.return_value = mock_dist_git
mock_bodhi = MagicMock()
mock_bodhi.is_branch_frozen.return_value = False
mock_bodhi.is_branch_stable.return_value = True
mock_set_bodhi.return_value = mock_bodhi
message = MagicMock()
message.body = {
"commit": {
"stats": {"files": {"dead.package": {"additions": 1, "deletions": 0}}},
"branch": "f41",
"repo": "example-repo",
"namespace": "example_ns",
"date": "2024-09-16T12:12:46+01:00",
}
}
message.topic = "git.receive"
self.toddler_cls.process(config, message)
self.toddler_cls.koji_session.packageListBlock.assert_not_called()
self.toddler_cls.koji_session.listPackages.assert_not_called()
assert caplog.records[-1].message == "Cannot block on stable branches: f41"
@patch("toddlers.plugins.koji_block_retired.bodhi.set_bodhi")
@patch("toddlers.plugins.koji_block_retired.pagure.set_pagure")
@patch("toddlers.plugins.koji_block_retired.KojiBlockRetired._create_session")
@ -232,6 +275,7 @@ class TestProcessBlockRetired:
mock_bodhi = MagicMock()
mock_bodhi.is_branch_frozen.return_value = False
mock_bodhi.is_branch_stable.return_value = False
mock_set_bodhi.return_value = mock_bodhi
message = MagicMock()
@ -270,6 +314,7 @@ class TestProcessBlockRetired:
mock_bodhi = MagicMock()
mock_bodhi.is_branch_frozen.return_value = False
mock_bodhi.is_branch_stable.return_value = False
mock_set_bodhi.return_value = mock_bodhi
caplog.set_level(logging.INFO)
@ -306,6 +351,7 @@ class TestProcessBlockRetired:
mock_bodhi = MagicMock()
mock_bodhi.is_branch_frozen.return_value = False
mock_bodhi.is_branch_stable.return_value = False
mock_set_bodhi.return_value = mock_bodhi
message = MagicMock()
@ -348,6 +394,7 @@ class TestProcessBlockRetired:
mock_bodhi = MagicMock()
mock_bodhi.is_branch_frozen.return_value = False
mock_bodhi.is_branch_stable.return_value = False
mock_set_bodhi.return_value = mock_bodhi
message = MagicMock()
@ -431,6 +478,7 @@ class TestProcessBlockRetired:
mock_bodhi = MagicMock()
mock_bodhi.is_branch_frozen.return_value = False
mock_bodhi.is_branch_stable.return_value = False
mock_set_bodhi.return_value = mock_bodhi
caplog.set_level(logging.INFO)
@ -1044,14 +1092,12 @@ class TestProcessBlockRetired:
)
@patch("requests.get")
@patch("toddlers.plugins.koji_block_retired.KojiBlockRetired.get_tag_from_target")
@patch("toddlers.plugins.koji_block_retired.bodhi.set_bodhi")
@patch("toddlers.plugins.koji_block_retired.KojiBlockRetired._create_session")
def test_playtime_call_frozen_branch(
self,
mock_create_session,
mock_set_bodhi,
mock_rawhide,
mock_req,
config,
caplog,
@ -1068,20 +1114,16 @@ class TestProcessBlockRetired:
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {
"rawhide": [
"f41": [
"fedora_pkg",
]
}
def req(*args, **kwargs):
if (
args[0]
== "https://src.fedoraproject.org/lookaside/retired_in_rawhide.json"
):
if args[0] == "https://src.fedoraproject.org/lookaside/retired_in_f41.json":
return mock_response
mock_req.side_effect = req
mock_rawhide.return_value = "f42"
self.toddler_cls.process(config, message)
self.toddler_cls.koji_session.listPackages.assert_not_called()
self.toddler_cls.koji_session.packageListBlock.assert_not_called()
@ -1091,6 +1133,54 @@ class TestProcessBlockRetired:
)
assert caplog.records[-2].message == "Branch rawhide is frozen, ignoring."
@patch("requests.get")
@patch(
"toddlers.plugins.koji_block_retired.KojiBlockRetired.adjust_releases_for_lookaside"
)
@patch("toddlers.plugins.koji_block_retired.bodhi.set_bodhi")
@patch("toddlers.plugins.koji_block_retired.KojiBlockRetired._create_session")
def test_playtime_call_stable_branch(
self,
mock_create_session,
mock_set_bodhi,
mock_adjusted,
mock_req,
config,
caplog,
):
mock_bodhi = MagicMock()
mock_bodhi.get_active_branches.return_value = [
"f41",
]
mock_bodhi.is_branch_frozen.return_value = False
mock_bodhi.is_branch_stable.return_value = True
mock_set_bodhi.return_value = mock_bodhi
caplog.set_level(logging.INFO)
message = MagicMock()
message.topic = "toddlers.trigger.koji_block_retired"
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {
"f41": [
"fedora_pkg",
]
}
def req(*args, **kwargs):
if args[0] == "https://src.fedoraproject.org/lookaside/retired_in_f41.json":
return mock_response
mock_req.side_effect = req
mock_adjusted.return_value = ["f41"]
self.toddler_cls.process(config, message)
self.toddler_cls.koji_session.listPackages.assert_not_called()
self.toddler_cls.koji_session.packageListBlock.assert_not_called()
assert (
caplog.records[-1].message
== "All packages that should be blocked in this run: {}"
)
assert caplog.records[-2].message == "Cannot block on stable branches: f41"
@pytest.mark.parametrize(
"releases",
[

View file

@ -202,3 +202,121 @@ class TestBodhiGetFrozenBranches:
result = self.bodhi.is_branch_frozen("f41")
assert result is False
class TestBodhiGetBranchInfo:
"""
Test class for `toddlers.utils.bodhi.Bodhi.get_frozen_branches` method.
"""
def setup_method(self):
"""
Setup method for the test class.
"""
config = {
"bodhi_url": "https://bodhi.fedoraproject.org",
}
self.bodhi = bodhi.set_bodhi(config)
self.bodhi._requests_session = Mock()
def test_get_branch_info(self):
"""
Assert that branch info isretrieved correctly from Bodhi.
"""
response_mock = Mock()
response_mock.status_code = 200
info = {"name": "F42", "long_name": "Fedora 42", "version": "42", "eol": None}
data = {
"releases": [
{"name": "F42", "long_name": "Fedora 42", "version": "42", "eol": None}
],
"page": 1,
"pages": 1,
"rows_per_page": 20,
"total": 1,
}
response_mock.json.return_value = data
self.bodhi._requests_session.get.return_value = response_mock
result = self.bodhi.get_branch_info("f42")
self.bodhi._requests_session.get.assert_called_once()
assert result == info
def test_get_branch_info_malformed_data(self):
"""
Assert that retrieving malformed json from bodhi is handled correctly.
"""
response_mock = Mock()
response_mock.status_code = 200
data = {}
response_mock.json.return_value = data
self.bodhi._requests_session.get.return_value = response_mock
expected_error = "Expected key not found in bodhi response."
with pytest.raises(BodhiError, match=expected_error):
self.bodhi.get_branch_info("f41")
self.bodhi._requests_session.get.assert_called_once()
def test_get_branch_info_not_ok(self):
"""
Assert that failing to retrieve branch info from bodhi is handled correctly.
"""
response_mock = Mock()
response_mock.status_code = 500
response_mock.headers = {"content-type": "application/json"}
self.bodhi._requests_session.get.return_value = response_mock
expected_error = "Couldn't retrieve branch info."
with pytest.raises(BodhiError, match=expected_error):
self.bodhi.get_branch_info("f41")
self.bodhi._requests_session.get.assert_called_once()
def test_is_branch_stable_no_eol(self):
get_stable_mock = Mock()
get_stable_mock.return_value = {
"name": "F42",
"long_name": "Fedora 42",
"version": "42",
"eol": None,
}
self.bodhi.get_branch_info = get_stable_mock
result = self.bodhi.is_branch_stable("f42")
assert result is False
def test_is_branch_stable_eol(self):
get_stable_mock = Mock()
get_stable_mock.return_value = {
"name": "F41",
"long_name": "Fedora 41",
"version": "41",
"eol": "2025-11-11",
}
self.bodhi.get_branch_info = get_stable_mock
result = self.bodhi.is_branch_stable("f41")
assert result is True
def test_is_branch_stable_returns_nothing(self):
get_stable_mock = Mock()
get_stable_mock.return_value = {}
self.bodhi.get_branch_info = get_stable_mock
result = self.bodhi.is_branch_stable("f41")
assert result is True

View file

@ -96,9 +96,15 @@ class KojiBlockRetired(ToddlerBase):
adjusted = self.adjust_releases_for_lookaside(active_releases)
needs_blocking = defaultdict(list)
for active_release in adjusted:
# Do not block on frozen branches
if self.bodhi.is_branch_frozen(active_release):
_log.info(f"Branch {active_release} is frozen, ignoring.")
continue
# Do not block on fedora stable branches (have EOL)
if re.match(r"^f", active_release):
if self.bodhi.is_branch_stable(active_release):
_log.info(f"Cannot block on stable branches: {active_release}")
continue
retired_url = f"{config['lookaside_url']}/retired_in_{active_release}.json"
response = requests.get(retired_url)
if not response.ok:
@ -228,6 +234,15 @@ class KojiBlockRetired(ToddlerBase):
)
return
# Retirements are allowed on rawhide, branched and EPEL
# and so is koji blocking.
# Exclude stable fedora branches from blocking.
# Stable branches are defined as those that have set EOL date.
if re.match(r"^f", branch):
if self.bodhi.is_branch_stable(branch):
_log.info(f"Cannot block on stable branches: {branch}")
return
if self.koji_session is None:
self._create_session() # pragma: no cover

View file

@ -197,3 +197,87 @@ class Bodhi:
if branch in frozen:
return True
return False
def get_branch_info(self, branch) -> dict:
"""
Get info about a particular branch from bodhi.
Params:
branch: string
Returns:
Dict containing branch info.
Raises:
toddlers.utils.exceptions.BodhiError when the issue couldn't be retrieved.
"""
api_url = f"{0}/list_releases/?name={branch}".format(self._bodhi_url)
log.debug("Retrieving branch info from bodhi.")
response = self._requests_session.get(api_url)
info = {}
if response.status_code == 200:
response_json = response.json()
try:
if response_json["releases"]:
(info,) = response_json["releases"]
return info
except KeyError:
raise BodhiError(
(
"Expected key not found in bodhi response. "
"Expected data: [<branch_name>]\n\n"
"Request to '{0}':\n\n"
"Response:\n"
"{1}\n\n"
"Status code: {2}"
).format(
api_url,
response_json,
response.status_code,
)
)
log.error(
"Error when retrieving branch info. " "Got status_code '%s'.",
response.status_code,
)
response_json = None
if response.headers.get("content-type") == "application/json":
response_json = response.json()
log.error("Received response: %s", response.json())
raise BodhiError(
(
"Couldn't retrieve branch info.\n\n"
"Request to '{0}':\n\n"
"Response:\n"
"{1}\n\n"
"Status code: {2}"
).format(
api_url,
response_json,
response.status_code,
)
)
def is_branch_stable(self, branch) -> bool:
"""Check whether the given branch is already stable => has end-of-life set.
Params:
branch: string
Returns:
bool
"""
branch_info = self.get_branch_info(branch)
try:
if branch_info["eol"]:
return True
return False
except KeyError:
log.info(
"Did not get relevant data from Bodhi to decide if the branch is stable"
)
return True