Skip to content

Commit 77f4690

Browse files
committed
Group transactions: fail on missing non-optional packages
If any default or mandatory package listed in a group does not exist, `raise dnf.exceptions.MarkingErrors`. This is a small change with a big impact and a lot of history. In the early days, dnf treated both 'package doesn't exist' and 'package exists but is not installable' as fatal errors. Shortly after Fedora and RHEL switched from yum to dnf, this was changed, because historically yum had not behaved this way, and our existing comps definitions had lots of missing packages; conditional and arch-specific comps entries also were not properly handled by all tools, so cleaning up comps was not possible. dnf was switched for a while to treat neither as fatal, which turned out to be too permissive, so eventually we gave it the same behaviour yum used to have, in #1038. These days, conditional and arch-specific comps entries work. I have just cleaned up Fedora's comps file for Rawhide, so there should be no 'missing' mandatory or default packages in any group: https://pagure.io/fedora-comps/pull-request/767 I don't know if RHEL's or CentOS's comps have been cleaned up, but if not, this presents an excellent opportunity to do it. Links to the history here: https://bugzilla.redhat.com/show_bug.cgi?id=1292892 https://bugzilla.redhat.com/show_bug.cgi?id=1427365 https://bugzilla.redhat.com/show_bug.cgi?id=1461539 #1038 Signed-off-by: Adam Williamson <awilliam@redhat.com>
1 parent b623eed commit 77f4690

5 files changed

Lines changed: 64 additions & 25 deletions

File tree

dnf/base.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
import gc
7676
import hawkey
7777
import itertools
78+
import libcomps
7879
import logging
7980
import math
8081
import os
@@ -1695,6 +1696,7 @@ def trans_remove(query, remove_query, comps_pkg):
16951696
(trans.upgrade, trans_upgrade),
16961697
(trans.remove, trans_remove))
16971698

1699+
missing_fatals = []
16981700
for (attr, fn) in attr_fn:
16991701
for comps_pkg in attr:
17001702
query_args = {'name': comps_pkg.name}
@@ -1706,11 +1708,16 @@ def trans_remove(query, remove_query, comps_pkg):
17061708
package_string = comps_pkg.name
17071709
if comps_pkg.basearchonly:
17081710
package_string += '.' + basearch
1709-
logger.warning(_('No match for group package "{}"').format(package_string))
1711+
if comps_pkg.type == libcomps.PACKAGE_TYPE_OPTIONAL:
1712+
logger.warning(_('No match for group package "{}"').format(package_string))
1713+
else:
1714+
missing_fatals.append(package_string)
17101715
continue
17111716
remove_query = fn(q, remove_query, comps_pkg)
17121717
self._goal.group_members.add(comps_pkg.name)
17131718

1719+
if missing_fatals:
1720+
raise dnf.exceptions.MarkingErrors(no_match_pkg_specs=missing_fatals)
17141721
self._remove_if_unneeded(remove_query)
17151722

17161723
def _build_comps_solver(self):

tests/repos/main_comps.xml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,25 @@
4343
<id>broken-group</id>
4444
<name>Broken Group</name>
4545
<packagelist>
46-
<packagereq type="mandatory">meaning-of-life</packagereq>
4746
<packagereq type="mandatory">lotus</packagereq>
4847
<packagereq type="default" requires="no-such-package">librita</packagereq>
4948
<packagereq type="optional">brokendeps</packagereq>
49+
<packagereq type="optional">no-such-package</packagereq>
50+
</packagelist>
51+
</group>
52+
<group>
53+
<id>broken-group-2</id>
54+
<name>Broken Group 2</name>
55+
<packagelist>
56+
<packagereq type="mandatory">no-such-package</packagereq>
57+
<packagereq type="default">no-such-package-2</packagereq>
5058
</packagelist>
5159
</group>
5260
<group>
5361
<id>missing-name-group</id>
5462
<name></name>
5563
<packagelist>
56-
<packagereq type="mandatory">meaning-of-life</packagereq>
64+
<packagereq type="mandatory">lotus</packagereq>
5765
</packagelist>
5866
</group>
5967
<category>

tests/support.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def mock_open(mock=None, data=None):
9595
MAIN_NSOLVABLES = 9
9696
UPDATES_NSOLVABLES = 4
9797
AVAILABLE_NSOLVABLES = MAIN_NSOLVABLES + UPDATES_NSOLVABLES
98-
TOTAL_GROUPS = 5
98+
TOTAL_GROUPS = 6
9999
TOTAL_NSOLVABLES = SYSTEM_NSOLVABLES + AVAILABLE_NSOLVABLES
100100

101101

tests/test_comps.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,15 @@ def test_group_packages(self):
107107
def test_iteration(self):
108108
comps = self.comps
109109
self.assertEqual([g.name for g in comps.groups_iter()],
110-
['Base', 'Solid Ground', "Pepper's", "Broken Group", None])
110+
['Base', 'Solid Ground', "Pepper's", "Broken Group", "Broken Group 2", None])
111111
self.assertEqual([c.name for c in comps.categories_iter()],
112112
['Base System'])
113113
g = dnf.util.first(comps.groups_iter())
114114
self.assertEqual(g.desc_by_lang['cs'], TRANSLATION)
115115

116116
def test_group_display_order(self):
117117
self.assertEqual([g.name for g in self.comps.groups],
118-
["Pepper's", 'Base', 'Solid Ground', 'Broken Group', None])
118+
["Pepper's", 'Base', 'Solid Ground', 'Broken Group', 'Broken Group 2', None])
119119

120120
def test_packages(self):
121121
comps = self.comps
@@ -127,7 +127,7 @@ def test_packages(self):
127127

128128
def test_size(self):
129129
comps = self.comps
130-
self.assertLength(comps, 7)
130+
self.assertLength(comps, 8)
131131
self.assertLength(comps.groups, tests.support.TOTAL_GROUPS)
132132
self.assertLength(comps.categories, 1)
133133
self.assertLength(comps.environments, 1)

tests/test_groups.py

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -199,27 +199,53 @@ def test_group_remove(self):
199199

200200
class ProblemGroupTest(tests.support.ResultTestCase):
201201
"""Test some cases involving problems in groups: packages that
202-
don't exist, and packages that exist but cannot be installed. The
203-
"broken" group lists three packages. "meaning-of-life", explicitly
204-
'default', does not exist. "lotus", implicitly 'mandatory' (no
205-
explicit type), exists and is installable. "brokendeps",
206-
explicitly 'optional', exists but has broken dependencies. See
202+
don't exist, and packages that exist but cannot be installed.
203+
broken_group lists four packages. "lotus", explicitly
204+
'mandatory', exists and is installable. "librita", explicitly
205+
'default', is a conditional entry that exists, but requires a
206+
package that doesn't exist. "brokendeps", explicitly 'optional',
207+
exists but has broken dependencies. "no-such-package", explicitly
208+
'optional', does not exist.
209+
broken_group_2 lists two packages, one explicitly mandatory and
210+
one explicitly default, neither of which exists.
211+
missing_name_group is a group which has no name. The package it
212+
contains exists and is not broken.
213+
See
207214
https://bugzilla.redhat.com/show_bug.cgi?id=1292892,
208215
https://bugzilla.redhat.com/show_bug.cgi?id=1337731,
209-
https://bugzilla.redhat.com/show_bug.cgi?id=1427365, and
210-
https://bugzilla.redhat.com/show_bug.cgi?id=1461539 for some of
211-
the background on this.
216+
https://bugzilla.redhat.com/show_bug.cgi?id=1427365,
217+
https://bugzilla.redhat.com/show_bug.cgi?id=1461539 and
218+
https://github.com/rpm-software-management/dnf/pull/1848
219+
for some of the background on this.
212220
"""
213221

214222
REPOS = ['main', 'broken_group']
215223
COMPS = True
216224
COMPS_SEED_PERSISTOR = True
217225

226+
def test_group_install_missing(self):
227+
"""Here we will test installing a group with two packages,
228+
one mandatory and one default, both non-existent. We should
229+
raise an error with both packages in it.
230+
"""
231+
comps_group = self.base.comps.group_by_pattern('Broken Group 2')
232+
swdb_group = self.history.group.get(comps_group.id)
233+
self.assertIsNone(swdb_group)
234+
235+
cnt = self.base.group_install(comps_group.id, ('mandatory', 'default', 'optional'))
236+
self.assertEqual(cnt, 2)
237+
238+
self._swdb_commit()
239+
with self.assertRaises(dnf.exceptions.MarkingErrors) as exccm:
240+
self.base.resolve()
241+
missing = set(exccm.no_match_pkg_specs)
242+
expected = set(['no-such-package', 'no-such-package-2'])
243+
self.assertEqual(missing, expected)
244+
218245
def test_group_install_broken_mandatory(self):
219246
"""Here we will test installing the group with only mandatory
220-
packages. We expect this to succeed, leaving out the
221-
non-existent 'meaning-of-life': it should also log a warning,
222-
but we don't test that.
247+
packages. We expect this to succeed. It shouldn't log any
248+
warnings either, but we don't test that.
223249
"""
224250
comps_group = self.base.comps.group_by_pattern('Broken Group')
225251
swdb_group = self.history.group.get(comps_group.id)
@@ -228,11 +254,8 @@ def test_group_install_broken_mandatory(self):
228254
cnt = self.base.group_install(comps_group.id, ('mandatory',))
229255
self._swdb_commit()
230256
self.base.resolve()
231-
# this counts packages *listed* in the group, so 2
232-
self.assertEqual(cnt, 2)
233-
257+
self.assertEqual(cnt, 1)
234258
inst, removed = self.installed_removed(self.base)
235-
# the above should work, but only 'lotus' actually installed
236259
self.assertLength(inst, 1)
237260
self.assertEmpty(removed)
238261

@@ -251,8 +274,8 @@ def test_group_install_broken_default(self):
251274
cnt = self.base.group_install(comps_group.id, ('mandatory', 'default'))
252275
self._swdb_commit()
253276
self.base.resolve()
254-
# this counts packages *listed* in the group, so 3
255-
self.assertEqual(cnt, 3)
277+
# this counts packages *listed* in the group, so 2
278+
self.assertEqual(cnt, 2)
256279

257280
inst, removed = self.installed_removed(self.base)
258281
# the above should work, but only 'lotus' actually installed
@@ -278,7 +301,8 @@ def test_group_install_broken_optional(self):
278301
def test_group_install_broken_optional_nonstrict(self):
279302
"""Here we test installing the group with optional packages
280303
included, but with strict=False. We expect this to succeed,
281-
skipping the package with broken dependencies.
304+
skipping the package with broken dependencies, and the package
305+
that doesn't exist.
282306
"""
283307
comps_group = self.base.comps.group_by_pattern('Broken Group')
284308
swdb_group = self.history.group.get(comps_group.id)

0 commit comments

Comments
 (0)