Skip to content

Commit da339e1

Browse files
drehakbocekm
authored andcommitted
Process events in order of releases (#372)
There are PES events where one follows up on another. One is from 7.6 release to 8.0 and the other from 8.0 to 8.1. This commit makes sure these "chained" events are processed correctly.
1 parent 8180500 commit da339e1

File tree

2 files changed

+138
-80
lines changed

2 files changed

+138
-80
lines changed

repos/system_upgrade/el7toel8/actors/peseventsscanner/libraries/library.py

Lines changed: 117 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414
Event = namedtuple('Event', ['action', # A string representing an event type (see EVENT_TYPES)
1515
'in_pkgs', # A dictionary with packages in format {<pkg_name>: <repository>}
1616
'out_pkgs', # A dictionary with packages in format {<pkg_name>: <repository>}
17+
'from_release', # A tuple representing a release in format (major, minor)
18+
'to_release', # A tuple representing a release in format (major, minor)
1719
'architectures' # A list of strings representing architectures
1820
])
1921

2022
EVENT_TYPES = ('Present', 'Removed', 'Deprecated', 'Replaced', 'Split', 'Merged', 'Moved', 'Renamed')
23+
RELEASES = ((7, 5), (7, 6), (7, 7), (7, 8), (8, 0), (8, 1)) # TODO: bad, bad hardcode
2124

2225

2326
def pes_events_scanner(pes_json_filepath):
@@ -28,8 +31,7 @@ def pes_events_scanner(pes_json_filepath):
2831
arch = api.current_actor().configuration.architecture
2932
arch_events = filter_events_by_architecture(events, arch)
3033
add_output_pkgs_to_transaction_conf(transaction_configuration, arch_events)
31-
filtered_events = get_events_for_installed_pkgs_only(arch_events, installed_pkgs)
32-
tasks = process_events(filtered_events, installed_pkgs)
34+
tasks = process_events(arch_events, installed_pkgs)
3335
filter_out_transaction_conf_pkgs(tasks, transaction_configuration)
3436
produce_messages(tasks)
3537

@@ -151,6 +153,13 @@ def parse_entry(entry):
151153
{
152154
'action': 3,
153155
'id': 15,
156+
'initial_release': { # can be None
157+
'z_stream': None,
158+
'major_version': 7,
159+
'tag': None,
160+
'os_name': 'RHEL',
161+
'minor_version': 7
162+
},
154163
'release': {
155164
'z_stream': None,
156165
'major_version': 8,
@@ -165,9 +174,9 @@ def parse_entry(entry):
165174
'name': 'espeak',
166175
'repository': 'rhel7-optional'
167176
}
168-
]
169-
}
170-
'out_packageset': { # 'out_packageset' can be None
177+
]
178+
},
179+
'out_packageset': { # can be None
171180
'set_id': 21,
172181
'package': [
173182
{
@@ -176,8 +185,10 @@ def parse_entry(entry):
176185
}
177186
]
178187
},
179-
'architectures': [ # 'can be empty'
188+
'architectures': [ # can be empty
180189
'x86_64',
190+
'aarch64',
191+
'ppc64le',
181192
's390x'
182193
]
183194
}
@@ -186,9 +197,11 @@ def parse_entry(entry):
186197
action = parse_action(entry['action'])
187198
in_pkgs = parse_packageset(entry.get('in_packageset') or {})
188199
out_pkgs = parse_packageset(entry.get('out_packageset') or {})
200+
from_release = parse_release(entry.get('initial_release') or {})
201+
to_release = parse_release(entry.get('release') or {})
189202
architectures = parse_architectures(entry.get('architectures') or [])
190203

191-
return Event(action, in_pkgs, out_pkgs, architectures)
204+
return Event(action, in_pkgs, out_pkgs, from_release, to_release, architectures)
192205

193206

194207
def parse_action(action_id):
@@ -208,32 +221,32 @@ def parse_packageset(packageset):
208221
return {p['name']: p['repository'].lower() for p in packageset.get('package', [])}
209222

210223

224+
def parse_release(release):
225+
return (release['major_version'], release['minor_version']) if release else (0, 0)
226+
227+
211228
def parse_architectures(architectures):
212229
for arch in architectures:
213230
if arch not in architecture.ARCH_ACCEPTED:
214231
raise ValueError('Found event with invalid architecture: {}'. format(arch))
215232
return architectures
216233

217234

218-
def get_events_for_installed_pkgs_only(events, installed_rh_pkgs):
219-
"""
220-
Get those PES events that have at least one of the event's "input" packages installed and signed by Red Hat.
221-
222-
:param events: List of Event tuples, where each event contains event type and input/output pkgs
223-
:param installed_rh_pkgs: Set of names of the installed Red Hat-signed packages
224-
:return: List of Event tuples, not including those events whose input packages are not installed
225-
"""
235+
def is_event_relevant(event, installed_pkgs, tasks):
236+
"""Determine if event is applicable given the installed packages and tasks planned so far."""
237+
for package in event.in_pkgs.keys():
238+
if package in tasks['to_remove']:
239+
return False
240+
if package not in installed_pkgs and package not in tasks['to_install']:
241+
return False
242+
return True
226243

227-
filtered = []
228-
for event in events:
229-
if is_at_least_one_event_input_pkg_installed(installed_rh_pkgs, event.in_pkgs):
230-
filtered.append(event)
231-
232-
return filtered
233244

234-
235-
def is_at_least_one_event_input_pkg_installed(installed_rh_pkgs, event_in_pkgs):
236-
return installed_rh_pkgs.intersection(set(event_in_pkgs.keys()))
245+
def add_packages_to_tasks(tasks, packages, key):
246+
verb = key[3:].upper() # 'to_remove' -> 'REMOVE' and so on
247+
if packages:
248+
api.current_logger().debug('{v} {p}'.format(v=verb, p=', '.join(packages)))
249+
tasks[key].update(packages)
237250

238251

239252
def process_events(events, installed_pkgs):
@@ -248,31 +261,83 @@ def process_events(events, installed_pkgs):
248261
tasks = { # Contains dicts in format {<pkg_name>: <repository>}
249262
'to_keep': {},
250263
'to_install': {},
251-
'to_remove': {}}
252-
for event in events:
253-
if event.action in ('Deprecated', 'Present'):
254-
# Add these packages to to_keep to make sure the repo they're in on RHEL 8 gets enabled
255-
tasks['to_keep'].update(event.in_pkgs)
256-
257-
if event.action == 'Moved':
258-
# Add these packages to to_keep to make sure the repo they're in on RHEL 8 gets enabled
259-
# We don't care about the "in_pkgs" as it contains always just one pkg - the same as the "out" pkg
260-
tasks['to_keep'].update(event.out_pkgs)
261-
262-
if event.action in ('Split', 'Merged', 'Renamed', 'Replaced'):
263-
non_installed_out_pkgs = filter_out_installed_pkgs(event.out_pkgs, installed_pkgs)
264-
tasks['to_install'].update(non_installed_out_pkgs)
265-
# Add the already installed "out" pkgs to to_keep to make sure the repo they're in on RHEL 8 gets enabled
266-
installed_out_pkgs = get_installed_out_pkgs(event.out_pkgs, installed_pkgs)
267-
tasks['to_keep'].update(installed_out_pkgs)
268-
if event.action in ('Split', 'Merged'):
269-
# Uninstall those RHEL 7 pkgs that are no longer on RHEL 8
270-
in_pkgs_without_out_pkgs = filter_out_out_pkgs(event.in_pkgs, event.out_pkgs)
271-
tasks['to_remove'].update(in_pkgs_without_out_pkgs)
272-
273-
if event.action in ('Renamed', 'Replaced', 'Removed'):
274-
# Uninstall those RHEL 7 pkgs that are no longer on RHEL 8
275-
tasks['to_remove'].update(event.in_pkgs)
264+
'to_remove': {}
265+
}
266+
for release in RELEASES:
267+
current = {
268+
'to_keep': {},
269+
'to_install': {},
270+
'to_remove': {}
271+
}
272+
release_events = [e for e in events if e.to_release == release]
273+
api.current_logger().debug('---- Processing {n} eligible events for release {r}'.format(
274+
n=len(release_events), r=release))
275+
for event in release_events:
276+
if is_event_relevant(event, installed_pkgs, tasks):
277+
if event.action in ('Deprecated', 'Present'):
278+
# Add these packages to to_keep to make sure the repo they're in on RHEL 8 gets enabled
279+
add_packages_to_tasks(current, event.in_pkgs, 'to_keep')
280+
281+
if event.action == 'Moved':
282+
# Add these packages to to_keep to make sure the repo they're in on RHEL 8 gets enabled
283+
# We don't care about the "in_pkgs" as it contains always just one pkg - the same as the "out" pkg
284+
add_packages_to_tasks(current, event.out_pkgs, 'to_keep')
285+
286+
if event.action in ('Split', 'Merged', 'Renamed', 'Replaced'):
287+
non_installed_out_pkgs = filter_out_installed_pkgs(event.out_pkgs, installed_pkgs)
288+
add_packages_to_tasks(current, non_installed_out_pkgs, 'to_install')
289+
# Add already installed "out" pkgs to to_keep to ensure the repo they're in on RHEL 8 gets enabled
290+
installed_out_pkgs = get_installed_event_pkgs(event.out_pkgs, installed_pkgs)
291+
add_packages_to_tasks(current, installed_out_pkgs, 'to_keep')
292+
293+
if event.action in ('Split', 'Merged'):
294+
# Uninstall those RHEL 7 pkgs that are no longer on RHEL 8
295+
in_pkgs_without_out_pkgs = filter_out_out_pkgs(event.in_pkgs, event.out_pkgs)
296+
add_packages_to_tasks(current, in_pkgs_without_out_pkgs, 'to_remove')
297+
298+
if event.action in ('Renamed', 'Replaced', 'Removed'):
299+
add_packages_to_tasks(current, event.in_pkgs, 'to_remove')
300+
301+
do_not_remove = set()
302+
for package in current['to_remove']:
303+
if package in tasks['to_keep']:
304+
api.current_logger().warning(
305+
'{p} :: {r} to be kept / currently removed - removing package'.format(
306+
p=package, r=current['to_remove'][package]))
307+
del tasks['to_keep'][package]
308+
elif package in tasks['to_install']:
309+
api.current_logger().warning(
310+
'{p} :: {r} to be installed / currently removed - ignoring tasks'.format(
311+
p=package, r=current['to_remove'][package]))
312+
del tasks['to_install'][package]
313+
do_not_remove.add(package)
314+
for package in do_not_remove:
315+
del current['to_remove'][package]
316+
317+
for package in current['to_install']:
318+
if package in tasks['to_remove']:
319+
api.current_logger().warning(
320+
'{p} :: {r} to be removed / currently installed - keeping package'.format(
321+
p=package, r=current['to_install'][package]))
322+
current['to_keep'][package] = current['to_install'][package]
323+
del tasks['to_remove'][package]
324+
del current['to_install'][package]
325+
326+
for package in current['to_keep']:
327+
if package in tasks['to_remove']:
328+
api.current_logger().warning(
329+
'{p} :: {r} to be removed / currently kept - keeping package'.format(
330+
p=package, r=current['to_keep'][package]))
331+
del tasks['to_remove'][package]
332+
333+
verbs = {'to_keep': 'kept', 'to_install': 'installed', 'to_remove': 'removed'}
334+
for key in 'to_keep', 'to_install', 'to_remove':
335+
for package in current[key]:
336+
if package in tasks[key]:
337+
api.current_logger().warning(
338+
'{p} :: {r} to be {v} TWICE - internal bug (not serious, continuing)'.format(
339+
p=package, r=current[key][package], v=verbs[key]))
340+
tasks[key].update(current[key])
276341

277342
map_repositories(tasks['to_install'])
278343
map_repositories(tasks['to_keep'])
@@ -287,14 +352,14 @@ def filter_out_installed_pkgs(event_out_pkgs, installed_pkgs):
287352
return {k: v for k, v in event_out_pkgs.items() if k not in installed_pkgs}
288353

289354

290-
def get_installed_out_pkgs(event_out_pkgs, installed_pkgs):
355+
def get_installed_event_pkgs(event_pkgs, installed_pkgs):
291356
"""
292-
Get those event's "out" packages which are already installed.
357+
Get those event's "in" or "out" packages which are already installed.
293358
294359
Even though we don't want to install the already installed pkgs, to be able to upgrade them to their RHEL 8 version
295360
we need to know in which repos they are and enable such repos.
296361
"""
297-
return {k: v for k, v in event_out_pkgs.items() if k in installed_pkgs}
362+
return {k: v for k, v in event_pkgs.items() if k in installed_pkgs}
298363

299364

300365
def filter_out_out_pkgs(event_in_pkgs, event_out_pkgs):

repos/system_upgrade/el7toel8/actors/peseventsscanner/tests/unit_test.py

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
from leapp.exceptions import StopActorExecution
66
from leapp.libraries.actor import library
77
from leapp.libraries.actor.library import (Event,
8+
RELEASES,
89
add_output_pkgs_to_transaction_conf,
910
filter_out_pkgs_in_blacklisted_repos,
1011
filter_events_by_architecture,
1112
get_events,
12-
get_events_for_installed_pkgs_only,
1313
map_repositories, parse_action,
1414
parse_entry, parse_packageset,
1515
parse_pes_events_file,
@@ -100,18 +100,6 @@ def test_parse_pes_events_file(current_actor_context):
100100
assert events[1].out_pkgs == {}
101101

102102

103-
def test_get_events_for_installed_pkgs_only(monkeypatch):
104-
events = [
105-
Event('Split', {'original': 'repo'}, {'split01': 'repo', 'split02': 'repo'}, []),
106-
Event('Removed', {'removed': 'repo'}, {}, [])]
107-
filtered = get_events_for_installed_pkgs_only(events, {'original'})
108-
109-
assert len(filtered) == 1
110-
assert filtered[0].action == 'Split'
111-
assert filtered[0].in_pkgs == {'original': 'repo'}
112-
assert filtered[0].out_pkgs == {'split01': 'repo', 'split02': 'repo'}
113-
114-
115103
def test_report_skipped_packages(monkeypatch):
116104
monkeypatch.setattr(api, 'produce', produce_mocked())
117105
monkeypatch.setattr(api, 'show_message', show_message_mocked())
@@ -170,10 +158,12 @@ def test_filter_out_pkgs_in_blacklisted_repos(monkeypatch):
170158
def test_resolve_conflicting_requests(monkeypatch):
171159
monkeypatch.setattr(library, 'map_repositories', lambda x: x)
172160
monkeypatch.setattr(library, 'filter_out_pkgs_in_blacklisted_repos', lambda x: x)
161+
monkeypatch.setattr(library, 'RELEASES', ((7, 5), (7, 6), (7, 7), (7, 8), (8, 0), (8, 1)))
162+
173163
events = [
174-
Event('Split', {'sip-devel': 'repo'}, {'python3-sip-devel': 'repo', 'sip': 'repo'}, []),
175-
Event('Split', {'sip': 'repo'}, {'python3-pyqt5-sip': 'repo', 'python3-sip': 'repo'}, [])]
176-
installed_pkgs = {'sip'}
164+
Event('Split', {'sip-devel': 'repo'}, {'python3-sip-devel': 'repo', 'sip': 'repo'}, (7, 6), (8, 0), []),
165+
Event('Split', {'sip': 'repo'}, {'python3-pyqt5-sip': 'repo', 'python3-sip': 'repo'}, (7, 6), (8, 0), [])]
166+
installed_pkgs = {'sip', 'sip-devel'}
177167

178168
tasks = process_events(events, installed_pkgs)
179169

@@ -211,12 +201,15 @@ def test_map_repositories(monkeypatch):
211201
def test_process_events(monkeypatch):
212202
monkeypatch.setattr(library, '_get_repositories_mapping', lambda: {'rhel8-repo': 'rhel8-mapped'})
213203
monkeypatch.setattr(library, 'get_repositories_blacklisted', get_repos_blacklisted_mocked(set()))
204+
monkeypatch.setattr(library, 'RELEASES', ((7, 5), (7, 6), (7, 7), (7, 8), (8, 0), (8, 1)))
214205

215206
events = [
216-
Event('Split', {'original': 'rhel7-repo'}, {'split01': 'rhel8-repo', 'split02': 'rhel8-repo'}, []),
217-
Event('Removed', {'removed': 'rhel7-repo'}, {}, []),
218-
Event('Present', {'present': 'rhel8-repo'}, {}, [])]
219-
tasks = process_events(events, set())
207+
Event('Split', {'original': 'rhel7-repo'}, {'split01': 'rhel8-repo', 'split02': 'rhel8-repo'},
208+
(7, 6), (8, 0), []),
209+
Event('Removed', {'removed': 'rhel7-repo'}, {}, (7, 6), (8, 0), []),
210+
Event('Present', {'present': 'rhel8-repo'}, {}, (7, 6), (8, 0), [])]
211+
installed_pkgs = {'original', 'removed', 'present'}
212+
tasks = process_events(events, installed_pkgs)
220213

221214
assert tasks['to_install'] == {'split02': 'rhel8-mapped', 'split01': 'rhel8-mapped'}
222215
assert tasks['to_remove'] == {'removed': 'rhel7-repo', 'original': 'rhel7-repo'}
@@ -252,10 +245,10 @@ def file_not_exists(_filepath):
252245

253246
def test_add_output_pkgs_to_transaction_conf():
254247
events = [
255-
Event('Split', {'split_in': 'repo'}, {'split_out1': 'repo', 'split_out2': 'repo'}, []),
256-
Event('Merged', {'merged_in1': 'repo', 'merged_in2': 'repo'}, {'merged_out': 'repo'}, []),
257-
Event('Renamed', {'renamed_in': 'repo'}, {'renamed_out': 'repo'}, []),
258-
Event('Replaced', {'replaced_in': 'repo'}, {'replaced_out': 'repo'}, []),
248+
Event('Split', {'split_in': 'repo'}, {'split_out1': 'repo', 'split_out2': 'repo'}, (7, 6), (8, 0), []),
249+
Event('Merged', {'merged_in1': 'repo', 'merged_in2': 'repo'}, {'merged_out': 'repo'}, (7, 6), (8, 0), []),
250+
Event('Renamed', {'renamed_in': 'repo'}, {'renamed_out': 'repo'}, (7, 6), (8, 0), []),
251+
Event('Replaced', {'replaced_in': 'repo'}, {'replaced_out': 'repo'}, (7, 6), (8, 0), []),
259252
]
260253

261254
conf_empty = RpmTransactionTasks()
@@ -285,10 +278,10 @@ def test_add_output_pkgs_to_transaction_conf():
285278

286279
def test_filter_events_by_architecture():
287280
events = [
288-
Event('Present', {'pkg1': 'repo'}, {}, ['arch1']),
289-
Event('Present', {'pkg2': 'repo'}, {}, ['arch2', 'arch1', 'arch3']),
290-
Event('Present', {'pkg3': 'repo'}, {}, ['arch2', 'arch3', 'arch4']),
291-
Event('Present', {'pkg4': 'repo'}, {}, [])
281+
Event('Present', {'pkg1': 'repo'}, {}, (7, 6), (8, 0), ['arch1']),
282+
Event('Present', {'pkg2': 'repo'}, {}, (7, 6), (8, 0), ['arch2', 'arch1', 'arch3']),
283+
Event('Present', {'pkg3': 'repo'}, {}, (7, 6), (8, 0), ['arch2', 'arch3', 'arch4']),
284+
Event('Present', {'pkg4': 'repo'}, {}, (7, 6), (8, 0), [])
292285
]
293286

294287
filtered = filter_events_by_architecture(events, 'arch1')

0 commit comments

Comments
 (0)