Skip to content

Commit a1dd480

Browse files
committed
handle priorities in kernel updates
fixes: #770
1 parent 78cfbf8 commit a1dd480

File tree

2 files changed

+250
-8
lines changed

2 files changed

+250
-8
lines changed

hosts/models.py

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -401,18 +401,28 @@ def find_kernel_updates(self, kernel_packages, repo_packages):
401401
update_ids = []
402402
self.reboot_required = False
403403

404+
# build hostrepos for priority filtering (same as find_host_repo_updates)
405+
hostrepos = None
406+
if self.host_repos_only:
407+
hostrepos_q = Q(repo__mirror__enabled=True,
408+
repo__mirror__refresh=True,
409+
repo__mirror__repo__enabled=True,
410+
host=self)
411+
hostrepos = HostRepo.objects.select_related(
412+
'host', 'repo').filter(hostrepos_q)
413+
404414
deb_kernels = kernel_packages.filter(packagetype='D')
405415
rpm_kernels = kernel_packages.filter(packagetype='R')
406416
arch_kernels = kernel_packages.filter(packagetype='A')
407417

408-
update_ids.extend(self._find_rpm_kernel_updates(rpm_kernels, repo_packages))
409-
update_ids.extend(self._find_deb_kernel_updates(deb_kernels, repo_packages))
410-
update_ids.extend(self._find_arch_kernel_updates(arch_kernels, repo_packages))
418+
update_ids.extend(self._find_rpm_kernel_updates(rpm_kernels, repo_packages, hostrepos))
419+
update_ids.extend(self._find_deb_kernel_updates(deb_kernels, repo_packages, hostrepos))
420+
update_ids.extend(self._find_arch_kernel_updates(arch_kernels, repo_packages, hostrepos))
411421

412422
self.save(update_fields=['reboot_required'])
413423
return update_ids
414424

415-
def _find_rpm_kernel_updates(self, kernel_packages, repo_packages):
425+
def _find_rpm_kernel_updates(self, kernel_packages, repo_packages, hostrepos):
416426

417427
update_ids = []
418428

@@ -436,9 +446,20 @@ def _find_rpm_kernel_updates(self, kernel_packages, repo_packages):
436446

437447
pu_q = Q(name=package.name)
438448

439-
# find repo highest for this kernel name
449+
# determine baseline priority from the installed package's repo
450+
priority = None
451+
if hostrepos is not None:
452+
best_repo = find_best_repo(package, hostrepos)
453+
if best_repo is not None:
454+
priority = best_repo.priority
455+
456+
# find repo highest for this kernel name, respecting priority
440457
repo_highest = None
441458
for pu in repo_packages.filter(pu_q):
459+
if priority is not None:
460+
pu_best_repo = find_best_repo(pu, hostrepos)
461+
if not pu_best_repo or pu_best_repo.priority < priority:
462+
continue
442463
if repo_highest is None or repo_highest.compare_version(pu) == -1:
443464
repo_highest = pu
444465

@@ -482,15 +503,26 @@ def _find_rpm_kernel_updates(self, kernel_packages, repo_packages):
482503

483504
return update_ids
484505

485-
def _find_arch_kernel_updates(self, kernel_packages, repo_packages):
506+
def _find_arch_kernel_updates(self, kernel_packages, repo_packages, hostrepos):
486507

487508
update_ids = []
488509

489510
for package in kernel_packages:
490511
pu_q = Q(name=package.name)
491512

513+
# determine baseline priority from the installed package's repo
514+
priority = None
515+
if hostrepos is not None:
516+
best_repo = find_best_repo(package, hostrepos)
517+
if best_repo is not None:
518+
priority = best_repo.priority
519+
492520
repo_highest = None
493521
for rp in repo_packages.filter(pu_q):
522+
if priority is not None:
523+
rp_best_repo = find_best_repo(rp, hostrepos)
524+
if not rp_best_repo or rp_best_repo.priority < priority:
525+
continue
494526
if repo_highest is None or repo_highest.compare_version(rp) == -1:
495527
repo_highest = rp
496528

@@ -522,7 +554,7 @@ def _find_arch_kernel_updates(self, kernel_packages, repo_packages):
522554

523555
return update_ids
524556

525-
def _find_deb_kernel_updates(self, kernel_packages, repo_packages):
557+
def _find_deb_kernel_updates(self, kernel_packages, repo_packages, hostrepos):
526558

527559
update_ids = []
528560
running_flavour = self._get_running_kernel_flavour()
@@ -535,6 +567,13 @@ def _find_deb_kernel_updates(self, kernel_packages, repo_packages):
535567
running_kernel_pkg = package
536568
break
537569

570+
# determine baseline priority from the running kernel's repo
571+
priority = None
572+
if hostrepos is not None and running_kernel_pkg is not None:
573+
best_repo = find_best_repo(running_kernel_pkg, hostrepos)
574+
if best_repo is not None:
575+
priority = best_repo.priority
576+
538577
processed_prefixes = set()
539578
for package in kernel_packages:
540579
pkg_name = package.name.name
@@ -563,9 +602,13 @@ def _find_deb_kernel_updates(self, kernel_packages, repo_packages):
563602
if running_flavour:
564603
name_filter &= Q(name__name__endswith=f'-{running_flavour}')
565604

566-
# find repo highest for this prefix+flavour
605+
# find repo highest for this prefix+flavour, respecting priority
567606
repo_highest = None
568607
for rp in repo_packages.filter(name_filter):
608+
if priority is not None:
609+
rp_best_repo = find_best_repo(rp, hostrepos)
610+
if not rp_best_repo or rp_best_repo.priority < priority:
611+
continue
569612
if repo_highest is None or repo_highest.compare_version(rp) == -1:
570613
repo_highest = rp
571614

hosts/tests/test_models.py

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,3 +697,202 @@ def test_arch_no_reboot_when_current(self):
697697

698698
host.refresh_from_db()
699699
self.assertFalse(host.reboot_required)
700+
701+
702+
@override_settings(
703+
CELERY_TASK_ALWAYS_EAGER=True,
704+
CACHES={'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'}}
705+
)
706+
class KernelPriorityTests(TestCase):
707+
"""Tests for kernel update priority filtering (backports)."""
708+
709+
def setUp(self):
710+
self.m_arch = MachineArchitecture.objects.create(name='x86_64')
711+
self.domain = Domain.objects.create(name='example.com')
712+
self.os_release = OSRelease.objects.create(
713+
name='Debian 12', codename='bookworm'
714+
)
715+
self.os_variant = OSVariant.objects.create(
716+
name='Debian GNU/Linux 12', osrelease=self.os_release
717+
)
718+
self.pkg_arch = PackageArchitecture.objects.create(name='amd64')
719+
720+
# installed kernel (from main repo)
721+
self.img_43_name = PackageName.objects.create(
722+
name='linux-image-6.1.0-43-amd64'
723+
)
724+
self.img_43 = Package.objects.create(
725+
name=self.img_43_name, arch=self.pkg_arch, epoch='',
726+
version='6.1.162-1', release='', packagetype='D'
727+
)
728+
729+
# backports kernel (newer version)
730+
self.img_bp_name = PackageName.objects.create(
731+
name='linux-image-6.12.73+deb13-amd64'
732+
)
733+
self.img_bp = Package.objects.create(
734+
name=self.img_bp_name, arch=self.pkg_arch, epoch='',
735+
version='6.12.73-1', release='', packagetype='D'
736+
)
737+
738+
# main repo (high priority)
739+
self.main_repo = Repository.objects.create(
740+
name='bookworm', repotype='D', arch=self.m_arch,
741+
)
742+
self.main_mirror = Mirror.objects.create(
743+
repo=self.main_repo,
744+
url='http://deb.debian.org/debian',
745+
)
746+
self.main_mirror.packages.add(self.img_43)
747+
748+
# backports repo (low priority)
749+
self.bp_repo = Repository.objects.create(
750+
name='bookworm-backports', repotype='D', arch=self.m_arch,
751+
)
752+
self.bp_mirror = Mirror.objects.create(
753+
repo=self.bp_repo,
754+
url='http://deb.debian.org/debian-backports',
755+
)
756+
self.bp_mirror.packages.add(self.img_bp)
757+
758+
def _create_host(self, main_priority, bp_priority, host_repos_only=True):
759+
host = Host.objects.create(
760+
hostname='debian.example.com',
761+
ipaddress='192.168.1.80',
762+
osvariant=self.os_variant,
763+
kernel='6.1.0-43-amd64',
764+
arch=self.m_arch,
765+
domain=self.domain,
766+
lastreport=timezone.now(),
767+
host_repos_only=host_repos_only,
768+
)
769+
host.packages.set([self.img_43])
770+
HostRepo.objects.create(
771+
host=host, repo=self.main_repo,
772+
enabled=True, priority=main_priority,
773+
)
774+
HostRepo.objects.create(
775+
host=host, repo=self.bp_repo,
776+
enabled=True, priority=bp_priority,
777+
)
778+
return host
779+
780+
def test_deb_backports_lower_priority_no_update(self):
781+
"""DEB: backports kernel with lower priority should NOT be flagged."""
782+
host = self._create_host(main_priority=500, bp_priority=100)
783+
repo_packages = Package.objects.filter(
784+
mirror__in=[self.main_mirror, self.bp_mirror]
785+
)
786+
kernel_packages = host.packages.filter(
787+
name__name__startswith='linux-image-'
788+
)
789+
790+
host.find_kernel_updates(kernel_packages, repo_packages)
791+
792+
self.assertEqual(host.updates.count(), 0)
793+
794+
def test_deb_backports_equal_priority_shows_update(self):
795+
"""DEB: backports kernel with equal priority SHOULD be flagged."""
796+
host = self._create_host(main_priority=500, bp_priority=500)
797+
repo_packages = Package.objects.filter(
798+
mirror__in=[self.main_mirror, self.bp_mirror]
799+
)
800+
kernel_packages = host.packages.filter(
801+
name__name__startswith='linux-image-'
802+
)
803+
804+
host.find_kernel_updates(kernel_packages, repo_packages)
805+
806+
self.assertEqual(host.updates.count(), 1)
807+
808+
def test_deb_priority_zero_no_filtering(self):
809+
"""DEB: priority 0 (unset) means no filtering — backward compat."""
810+
host = self._create_host(main_priority=0, bp_priority=0)
811+
repo_packages = Package.objects.filter(
812+
mirror__in=[self.main_mirror, self.bp_mirror]
813+
)
814+
kernel_packages = host.packages.filter(
815+
name__name__startswith='linux-image-'
816+
)
817+
818+
host.find_kernel_updates(kernel_packages, repo_packages)
819+
820+
self.assertEqual(host.updates.count(), 1)
821+
822+
def test_deb_host_repos_only_false_no_filtering(self):
823+
"""DEB: host_repos_only=False skips priority filtering entirely."""
824+
host = self._create_host(
825+
main_priority=500, bp_priority=100, host_repos_only=False,
826+
)
827+
repo_packages = Package.objects.filter(
828+
mirror__in=[self.main_mirror, self.bp_mirror]
829+
)
830+
kernel_packages = host.packages.filter(
831+
name__name__startswith='linux-image-'
832+
)
833+
834+
host.find_kernel_updates(kernel_packages, repo_packages)
835+
836+
self.assertEqual(host.updates.count(), 1)
837+
838+
def test_rpm_backports_lower_priority_no_update(self):
839+
"""RPM: kernel from lower-priority repo should NOT be flagged."""
840+
rpm_arch = PackageArchitecture.objects.create(name='x86_64_rpm')
841+
kernel_name = PackageName.objects.create(name='kernel-core')
842+
installed = Package.objects.create(
843+
name=kernel_name, arch=rpm_arch, epoch='0',
844+
version='5.14.0', release='362.el9', packagetype='R'
845+
)
846+
newer = Package.objects.create(
847+
name=kernel_name, arch=rpm_arch, epoch='0',
848+
version='5.14.0', release='503.el9', packagetype='R'
849+
)
850+
851+
base_repo = Repository.objects.create(
852+
name='baseos', repotype='R', arch=self.m_arch,
853+
)
854+
base_mirror = Mirror.objects.create(
855+
repo=base_repo, url='http://repo.example.com/baseos',
856+
)
857+
base_mirror.packages.add(installed)
858+
859+
extra_repo = Repository.objects.create(
860+
name='extras', repotype='R', arch=self.m_arch,
861+
)
862+
extra_mirror = Mirror.objects.create(
863+
repo=extra_repo, url='http://repo.example.com/extras',
864+
)
865+
extra_mirror.packages.add(newer)
866+
867+
os_release = OSRelease.objects.create(
868+
name='Rocky Linux 9', codename='rl9'
869+
)
870+
os_variant = OSVariant.objects.create(
871+
name='Rocky Linux 9.4', osrelease=os_release
872+
)
873+
host = Host.objects.create(
874+
hostname='rocky.example.com',
875+
ipaddress='192.168.1.90',
876+
osvariant=os_variant,
877+
kernel='5.14.0-362.el9',
878+
arch=self.m_arch,
879+
domain=self.domain,
880+
lastreport=timezone.now(),
881+
host_repos_only=True,
882+
)
883+
host.packages.set([installed])
884+
HostRepo.objects.create(
885+
host=host, repo=base_repo, enabled=True, priority=500,
886+
)
887+
HostRepo.objects.create(
888+
host=host, repo=extra_repo, enabled=True, priority=100,
889+
)
890+
891+
repo_packages = Package.objects.filter(
892+
mirror__in=[base_mirror, extra_mirror]
893+
)
894+
kernel_packages = host.packages.filter(name=kernel_name)
895+
896+
host.find_kernel_updates(kernel_packages, repo_packages)
897+
898+
self.assertEqual(host.updates.count(), 0)

0 commit comments

Comments
 (0)