Skip to content

Commit 418f713

Browse files
authored
Merge pull request #650 from xcp-ng/gln/package-consistency-check-soun
update: report updated packages and verify consistency across hosts
2 parents a54de2a + dfe8720 commit 418f713

1 file changed

Lines changed: 54 additions & 0 deletions

File tree

lib/tools/tasks/update.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,54 @@
1313

1414
from .. import logger
1515

16+
def _capture_packages(pools: list[Pool]) -> dict[Host, set[str]]:
17+
"""Snapshot the installed packages of every host in the pools."""
18+
return {h: set(h.packages()) for p in pools for h in p.hosts}
19+
20+
def _filter_packages(pkgs: set[str]) -> set[str]:
21+
return {p for p in pkgs if not p.startswith("gpg-pubkey-")}
22+
23+
def _format_packages(pkgs: list[str]) -> str:
24+
return "\n".join(f" - {p}" for p in pkgs)
25+
26+
def _report_updated(before: dict[Host, set[str]], after: dict[Host, set[str]]) -> None:
27+
"""Log a summary of the packages that were updated on each host."""
28+
updated = {
29+
h: _filter_packages(after[h] - pkgs) for h, pkgs in before.items()
30+
}
31+
common_updated = set.intersection(*updated.values()) if updated else set()
32+
33+
if not common_updated:
34+
logger.info("No packages were updated on any host.")
35+
return
36+
logger.info(
37+
f"Updated packages on all hosts ({len(common_updated)}):\n"
38+
f"{_format_packages(sorted(common_updated))}"
39+
)
40+
for h, pkgs in updated.items():
41+
extra = sorted(pkgs - common_updated)
42+
if extra:
43+
logger.info(
44+
f"Additional packages on [{h}] ({len(extra)}):\n"
45+
f"{_format_packages(extra)}"
46+
)
47+
48+
def _check_consistency(packages: dict[Host, set[str]]) -> None:
49+
"""Warn if not all hosts end up with the same set of packages."""
50+
common_set = set.intersection(*packages.values())
51+
inconsistent = {
52+
h: _filter_packages(p) - _filter_packages(common_set)
53+
for h, p in packages.items() if _filter_packages(p) != _filter_packages(common_set)
54+
}
55+
if inconsistent:
56+
lines = [
57+
f"Not all hosts have the same set of packages "
58+
f"(reference: common set of {len(packages)} hosts):"
59+
]
60+
for h, extra_pkgs in inconsistent.items():
61+
lines.append(f" [{h}] additional packages:\n{_format_packages(sorted(extra_pkgs))}")
62+
logger.warning("\n".join(lines))
63+
1664
def update_pools(inventory: Inventory) -> None:
1765
"""Updates hosts in pool(s).
1866
@@ -43,6 +91,8 @@ def update_pools(inventory: Inventory) -> None:
4391
except NotAMasterHostError:
4492
logger.warning(f"[{host}] Skipping: not a master host")
4593

94+
before_packages = _capture_packages(pools)
95+
4696
# update master hosts
4797
with ThreadPoolExecutor() as executor:
4898
future_masters = {executor.submit(
@@ -80,6 +130,10 @@ def update_pools(inventory: Inventory) -> None:
80130
)
81131
raise exc
82132

133+
after_packages = _capture_packages(pools)
134+
_report_updated(before_packages, after_packages)
135+
_check_consistency(after_packages)
136+
83137
# Snapshot creation
84138
for hosting_pool, nested in nested_hosts.items():
85139
pool = Pool(hosting_pool) # mandatory for getting an host instance

0 commit comments

Comments
 (0)