Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package openQA for openSUSE:Factory checked in at 2025-05-20 09:32:19 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/openQA (Old) and /work/SRC/openSUSE:Factory/.openQA.new.30101 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "openQA" Tue May 20 09:32:19 2025 rev:692 rq:1277800 version:5.1747282262.9a4e6bb5 Changes: -------- --- /work/SRC/openSUSE:Factory/openQA/openQA.changes 2025-05-14 17:01:26.675837091 +0200 +++ /work/SRC/openSUSE:Factory/.openQA.new.30101/openQA.changes 2025-05-20 09:34:04.931164181 +0200 @@ -1,0 +2,17 @@ +Thu May 15 14:14:46 UTC 2025 - ok...@suse.com + +- Update to version 5.1747282262.9a4e6bb5: + * load-templates: with --clean, empty job group YAML templates + * Fix phrasing in jobs comment carry over unit test + * Bump debug from 4.4.0 to 4.4.1 + * Bump synckit from 0.11.4 to 0.11.5 + * Avoid workers getting stuck with old jobs + * Avoid duplicate `use Mojo::JSON` + * Follow consistent database DUMP_FOLDER target directory + * Create new systemd service and timer for database dump and cleanup + * dump-templates: dump job groups as they exist, fix group checks + * t: load-templates: check harder for what gets loaded + * load-templates: job groups: simplify, don't error on group exists + * load-templates: fix loading of job templates + +------------------------------------------------------------------- Old: ---- openQA-5.1747157239.98c95eac.obscpio New: ---- openQA-5.1747282262.9a4e6bb5.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ openQA-client-test.spec ++++++ --- /var/tmp/diff_new_pack.SYkaFg/_old 2025-05-20 09:34:05.827201720 +0200 +++ /var/tmp/diff_new_pack.SYkaFg/_new 2025-05-20 09:34:05.831201887 +0200 @@ -18,7 +18,7 @@ %define short_name openQA-client Name: %{short_name}-test -Version: 5.1747157239.98c95eac +Version: 5.1747282262.9a4e6bb5 Release: 0 Summary: Test package for %{short_name} License: GPL-2.0-or-later ++++++ openQA-devel-test.spec ++++++ --- /var/tmp/diff_new_pack.SYkaFg/_old 2025-05-20 09:34:05.855202893 +0200 +++ /var/tmp/diff_new_pack.SYkaFg/_new 2025-05-20 09:34:05.855202893 +0200 @@ -18,7 +18,7 @@ %define short_name openQA-devel Name: %{short_name}-test -Version: 5.1747157239.98c95eac +Version: 5.1747282262.9a4e6bb5 Release: 0 Summary: Test package for %{short_name} License: GPL-2.0-or-later ++++++ openQA-test.spec ++++++ --- /var/tmp/diff_new_pack.SYkaFg/_old 2025-05-20 09:34:05.883204066 +0200 +++ /var/tmp/diff_new_pack.SYkaFg/_new 2025-05-20 09:34:05.887204233 +0200 @@ -18,7 +18,7 @@ %define short_name openQA Name: %{short_name}-test -Version: 5.1747157239.98c95eac +Version: 5.1747282262.9a4e6bb5 Release: 0 Summary: Test package for openQA License: GPL-2.0-or-later ++++++ openQA-worker-test.spec ++++++ --- /var/tmp/diff_new_pack.SYkaFg/_old 2025-05-20 09:34:05.919205574 +0200 +++ /var/tmp/diff_new_pack.SYkaFg/_new 2025-05-20 09:34:05.919205574 +0200 @@ -18,7 +18,7 @@ %define short_name openQA-worker Name: %{short_name}-test -Version: 5.1747157239.98c95eac +Version: 5.1747282262.9a4e6bb5 Release: 0 Summary: Test package for %{short_name} License: GPL-2.0-or-later ++++++ openQA.spec ++++++ --- /var/tmp/diff_new_pack.SYkaFg/_old 2025-05-20 09:34:05.951206914 +0200 +++ /var/tmp/diff_new_pack.SYkaFg/_new 2025-05-20 09:34:05.955207083 +0200 @@ -20,6 +20,7 @@ %define openqa_extra_services openqa-gru.service openqa-websockets.service openqa-scheduler.service openqa-enqueue-audit-event-cleanup.service openqa-enqueue-audit-event-cleanup.timer openqa-enqueue-asset-cleanup.service openqa-enqueue-git-auto-update.service openqa-enqueue-asset-cleanup.timer openqa-enqueue-result-cleanup.service openqa-enqueue-result-cleanup.timer openqa-enqueue-bug-cleanup.service openqa-enqueue-bug-cleanup.timer openqa-enqueue-git-auto-update.timer openqa-enqueue-needle-ref-cleanup.service openqa-enqueue-needle-ref-cleanup.timer %define openqa_services %{openqa_main_service} %{openqa_extra_services} %define openqa_worker_services openqa-worker.target openqa-slirpvde.service openqa-vde_switch.service openqa-worker-cacheservice.service openqa-worker-cacheservice-minion.service +%define openqa_localdb_services openqa-setup-db.service openqa-dump-db.service openqa-dump-db.timer %if %{undefined tmpfiles_create} %define tmpfiles_create() \ %{_bindir}/systemd-tmpfiles --create %{?*} || : \ @@ -89,7 +90,7 @@ %define devel_requires %devel_no_selenium_requires chromedriver Name: openQA -Version: 5.1747157239.98c95eac +Version: 5.1747282262.9a4e6bb5 Release: 0 Summary: The openQA web-frontend, scheduler and tools License: GPL-2.0-or-later @@ -552,13 +553,13 @@ %service_del_postun openqa-continuous-update.timer %post local-db -%service_add_post openqa-setup-db.service +%service_add_post %{openqa_localdb_services} %preun local-db -%service_del_preun openqa-setup-db.service +%service_del_preun %{openqa_localdb_services} %postun local-db -%service_del_postun openqa-setup-db.service +%service_del_postun %{openqa_localdb_services} %files %doc README.asciidoc @@ -797,6 +798,8 @@ %files local-db %{_unitdir}/openqa-setup-db.service +%{_unitdir}/openqa-dump-db.service +%{_unitdir}/openqa-dump-db.timer %{_unitdir}/openqa-gru.service.requires/postgresql.service %{_unitdir}/openqa-scheduler.service.requires/postgresql.service %{_unitdir}/openqa-websockets.service.requires/postgresql.service @@ -804,6 +807,7 @@ %{_datadir}/openqa/script/dump-db %{_bindir}/openqa-setup-db %{_bindir}/openqa-dump-db +%dir %attr(0755,postgres,root) %{_localstatedir}/lib/openqa/backup %files single-instance ++++++ node_modules.obscpio ++++++ Binary files old/debug-4.4.0.tgz and new/debug-4.4.0.tgz differ Binary files old/debug-4.4.1.tgz and new/debug-4.4.1.tgz differ Binary files old/synckit-0.11.4.tgz and new/synckit-0.11.4.tgz differ Binary files old/synckit-0.11.5.tgz and new/synckit-0.11.5.tgz differ ++++++ node_modules.spec.inc ++++++ --- /var/tmp/diff_new_pack.SYkaFg/_old 2025-05-20 09:34:06.591233729 +0200 +++ /var/tmp/diff_new_pack.SYkaFg/_new 2025-05-20 09:34:06.595233896 +0200 @@ -80,7 +80,7 @@ Source1079: https://registry.npmjs.org/dagre-d3/-/dagre-d3-0.6.4.tgz#/dagre-d3-0.6.4.tgz Source1080: https://registry.npmjs.org/datatables.net/-/datatables.net-2.3.0.tgz#/datatables.net-2.3.0.tgz Source1081: https://registry.npmjs.org/datatables.net-bs5/-/datatables.net-bs5-2.3.0.tgz#/datatables.net-bs5-2.3.0.tgz -Source1082: https://registry.npmjs.org/debug/-/debug-4.4.0.tgz#/debug-4.4.0.tgz +Source1082: https://registry.npmjs.org/debug/-/debug-4.4.1.tgz#/debug-4.4.1.tgz Source1083: https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz#/deep-is-0.1.4.tgz Source1084: https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz#/delaunator-5.0.1.tgz Source1085: https://registry.npmjs.org/depd/-/depd-2.0.0.tgz#/depd-2.0.0.tgz @@ -201,7 +201,7 @@ Source1200: https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz#/statuses-2.0.1.tgz Source1201: https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz#/strip-json-comments-3.1.1.tgz Source1202: https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz#/supports-color-7.2.0.tgz -Source1203: https://registry.npmjs.org/synckit/-/synckit-0.11.4.tgz#/synckit-0.11.4.tgz +Source1203: https://registry.npmjs.org/synckit/-/synckit-0.11.5.tgz#/synckit-0.11.5.tgz Source1204: https://registry.npmjs.org/timeago/-/timeago-1.6.7.tgz#/timeago-1.6.7.tgz Source1205: https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz#/toidentifier-1.0.1.tgz Source1206: https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz#/tslib-2.8.1.tgz ++++++ openQA-5.1747157239.98c95eac.obscpio -> openQA-5.1747282262.9a4e6bb5.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1747157239.98c95eac/Makefile new/openQA-5.1747282262.9a4e6bb5/Makefile --- old/openQA-5.1747157239.98c95eac/Makefile 2025-05-13 19:27:19.000000000 +0200 +++ new/openQA-5.1747282262.9a4e6bb5/Makefile 2025-05-15 06:11:02.000000000 +0200 @@ -71,7 +71,7 @@ install -m 644 -D --target-directory="$(DESTDIR)/usr/share/openqa/$${f%/*}" "$$f";\ done - for i in db images testresults pool/1 cache webui/cache; do \ + for i in db images testresults pool/1 cache webui/cache backup; do \ mkdir -p "$(DESTDIR)"/var/lib/openqa/$$i ;\ done # shared dirs between openQA web and workers + compatibility links diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1747157239.98c95eac/dist/rpm/openQA.spec new/openQA-5.1747282262.9a4e6bb5/dist/rpm/openQA.spec --- old/openQA-5.1747157239.98c95eac/dist/rpm/openQA.spec 2025-05-13 19:27:19.000000000 +0200 +++ new/openQA-5.1747282262.9a4e6bb5/dist/rpm/openQA.spec 2025-05-15 06:11:02.000000000 +0200 @@ -20,6 +20,7 @@ %define openqa_extra_services openqa-gru.service openqa-websockets.service openqa-scheduler.service openqa-enqueue-audit-event-cleanup.service openqa-enqueue-audit-event-cleanup.timer openqa-enqueue-asset-cleanup.service openqa-enqueue-git-auto-update.service openqa-enqueue-asset-cleanup.timer openqa-enqueue-result-cleanup.service openqa-enqueue-result-cleanup.timer openqa-enqueue-bug-cleanup.service openqa-enqueue-bug-cleanup.timer openqa-enqueue-git-auto-update.timer openqa-enqueue-needle-ref-cleanup.service openqa-enqueue-needle-ref-cleanup.timer %define openqa_services %{openqa_main_service} %{openqa_extra_services} %define openqa_worker_services openqa-worker.target openqa-slirpvde.service openqa-vde_switch.service openqa-worker-cacheservice.service openqa-worker-cacheservice-minion.service +%define openqa_localdb_services openqa-setup-db.service openqa-dump-db.service openqa-dump-db.timer %if %{undefined tmpfiles_create} %define tmpfiles_create() \ %{_bindir}/systemd-tmpfiles --create %{?*} || : \ @@ -553,13 +554,13 @@ %service_del_postun openqa-continuous-update.timer %post local-db -%service_add_post openqa-setup-db.service +%service_add_post %{openqa_localdb_services} %preun local-db -%service_del_preun openqa-setup-db.service +%service_del_preun %{openqa_localdb_services} %postun local-db -%service_del_postun openqa-setup-db.service +%service_del_postun %{openqa_localdb_services} %files %doc README.asciidoc @@ -798,6 +799,8 @@ %files local-db %{_unitdir}/openqa-setup-db.service +%{_unitdir}/openqa-dump-db.service +%{_unitdir}/openqa-dump-db.timer %{_unitdir}/openqa-gru.service.requires/postgresql.service %{_unitdir}/openqa-scheduler.service.requires/postgresql.service %{_unitdir}/openqa-websockets.service.requires/postgresql.service @@ -805,6 +808,7 @@ %{_datadir}/openqa/script/dump-db %{_bindir}/openqa-setup-db %{_bindir}/openqa-dump-db +%dir %attr(0755,postgres,root) %{_localstatedir}/lib/openqa/backup %files single-instance diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1747157239.98c95eac/docs/Installing.asciidoc new/openQA-5.1747282262.9a4e6bb5/docs/Installing.asciidoc --- old/openQA-5.1747157239.98c95eac/docs/Installing.asciidoc 2025-05-13 19:27:19.000000000 +0200 +++ new/openQA-5.1747282262.9a4e6bb5/docs/Installing.asciidoc 2025-05-15 06:11:02.000000000 +0200 @@ -1205,6 +1205,19 @@ Note that jobs marked as incomplete by the stale job detection are not affected by this configuration and cloned in any case. +=== Enable automatic database backup +[id="automatic_database_cleanup"] + +An optional systemd service, `openqa-dump-db.service`, can be enabled to +perform daily database backups. This service is triggered by the +`openqa-dump-db.timer`. To enable automatic database backup, run: + +[source,sh] +-------------------------------------------------------------------------------- +systemctl enable --now openqa-dump-db.timer +-------------------------------------------------------------------------------- +Backups are stored at `/var/lib/openqa/backup`. + [id="auditing"] == Auditing - tracking openQA changes diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1747157239.98c95eac/lib/OpenQA/Schema/Result/Jobs.pm new/openQA-5.1747282262.9a4e6bb5/lib/OpenQA/Schema/Result/Jobs.pm --- old/openQA-5.1747157239.98c95eac/lib/OpenQA/Schema/Result/Jobs.pm 2025-05-13 19:27:19.000000000 +0200 +++ new/openQA-5.1747282262.9a4e6bb5/lib/OpenQA/Schema/Result/Jobs.pm 2025-05-15 06:11:02.000000000 +0200 @@ -4,7 +4,6 @@ package OpenQA::Schema::Result::Jobs; use Mojo::Base 'DBIx::Class::Core', -signatures; -use Mojo::JSON 'encode_json'; use Fcntl; use DateTime; use OpenQA::Constants qw(WORKER_COMMAND_ABORT WORKER_COMMAND_CANCEL); @@ -30,7 +29,7 @@ use File::Temp 'tempdir'; use Mojo::Collection; use Mojo::File qw(tempfile path); -use Mojo::JSON 'decode_json'; +use Mojo::JSON qw(encode_json decode_json); use Data::Dump 'dump'; use Text::Diff; use OpenQA::File; @@ -1758,23 +1757,29 @@ =cut sub carry_over_bugrefs ($self) { - if (my $group = $self->group) { return undef unless $group->carry_over_bugrefs } - return undef unless my $prev = $self->_carry_over_candidate; + try { + if (my $group = $self->group) { return undef unless $group->carry_over_bugrefs } + return undef unless my $prev = $self->_carry_over_candidate; - my $comments = $prev->comments->search({}, {order_by => {-desc => 'me.id'}}); - for my $comment ($comments->all) { - next if !$comment->bugref && !exists($comment->text_flags->{carryover}); - my $text = $comment->text; - my $prev_id = $prev->id; - $text .= "\n\n(Automatic carryover from t#$prev_id)" if $text !~ qr/Automatic (takeover|carryover)/; - $text .= "\n(The hook script will not be executed.)" - if $text !~ qr/The hook script will not be executed/ && defined $self->hook_script; - $text .= "\n" unless substr($text, -1, 1) eq "\n"; - my %newone = (text => $text, user_id => $comment->user_id); - my $comment = $self->comments->create_with_event(\%newone, {taken_over_from_job_id => $prev_id}); - try { $comment->handle_special_contents } - catch ($e) { log_info "Unable to evaluate contents of taken-over comment: $e" } - return 1; + my $comments = $prev->comments->search({}, {order_by => {-desc => 'me.id'}}); + for my $comment ($comments->all) { + next if !$comment->bugref && !exists($comment->text_flags->{carryover}); + my $text = $comment->text; + my $prev_id = $prev->id; + $text .= "\n\n(Automatic carryover from t#$prev_id)" if $text !~ qr/Automatic (takeover|carryover)/; + $text .= "\n(The hook script will not be executed.)" + if $text !~ qr/The hook script will not be executed/ && defined $self->hook_script; + $text .= "\n" unless substr($text, -1, 1) eq "\n"; + my %newone = (text => $text, user_id => $comment->user_id); + my $comment = $self->comments->create_with_event(\%newone, {taken_over_from_job_id => $prev_id}); + try { $comment->handle_special_contents } + catch ($e) { log_info "Unable to evaluate contents of taken-over comment: $e" } + return 1; + } + } + catch ($e) { + my $job_id = $self->id; + log_warning "Unable to carry-over bugrefs of job $job_id: $e"; } return undef; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1747157239.98c95eac/lib/OpenQA/WebAPI/Controller/API/V1/JobGroup.pm new/openQA-5.1747282262.9a4e6bb5/lib/OpenQA/WebAPI/Controller/API/V1/JobGroup.pm --- old/openQA-5.1747157239.98c95eac/lib/OpenQA/WebAPI/Controller/API/V1/JobGroup.pm 2025-05-13 19:27:19.000000000 +0200 +++ new/openQA-5.1747282262.9a4e6bb5/lib/OpenQA/WebAPI/Controller/API/V1/JobGroup.pm 2025-05-15 06:11:02.000000000 +0200 @@ -3,6 +3,7 @@ package OpenQA::WebAPI::Controller::API::V1::JobGroup; use Mojo::Base 'Mojolicious::Controller', -signatures; +use Mojo::JSON; use Feature::Compat::Try; use OpenQA::Schema::Result::JobGroups; @@ -219,7 +220,10 @@ my $check = $self->check_top_level_group; if ($check != 0) { return $self->render( - json => {error => 'Unable to create group with existing name ' . $validation->param('name')}, + json => { + error => 'Unable to create group with existing name ' . $validation->param('name'), + already_exists => Mojo::JSON->true + }, status => 500 ); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1747157239.98c95eac/lib/OpenQA/WebAPI/Controller/API/V1/Worker.pm new/openQA-5.1747282262.9a4e6bb5/lib/OpenQA/WebAPI/Controller/API/V1/Worker.pm --- old/openQA-5.1747157239.98c95eac/lib/OpenQA/WebAPI/Controller/API/V1/Worker.pm 2025-05-13 19:27:19.000000000 +0200 +++ new/openQA-5.1747282262.9a4e6bb5/lib/OpenQA/WebAPI/Controller/API/V1/Worker.pm 2025-05-15 06:11:02.000000000 +0200 @@ -148,11 +148,11 @@ my $worker = $job->assigned_worker // $job->worker; my $worker_info = defined $worker ? ('worker ' . $worker->name) : 'worker'; $job->set_property('JOBTOKEN'); - $job->auto_duplicate; - $job->done( - result => OpenQA::Jobs::Constants::INCOMPLETE, - reason => "abandoned: associated $worker_info re-connected but abandoned the job", - ); + try { $job->auto_duplicate } + catch ($e) { log_warning("Unable to duplicate job $job_id after being abandoned by $worker_info: $e") } + my $reason = "abandoned: associated $worker_info re-connected but abandoned the job"; + try { $job->done(result => INCOMPLETE, reason => $reason) } + catch ($e) { log_warning("Unable to incomplete job $job_id after being abandoned by $worker_info: $e") } return 1; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1747157239.98c95eac/package-lock.json new/openQA-5.1747282262.9a4e6bb5/package-lock.json --- old/openQA-5.1747157239.98c95eac/package-lock.json 2025-05-13 19:27:19.000000000 +0200 +++ new/openQA-5.1747282262.9a4e6bb5/package-lock.json 2025-05-15 06:11:02.000000000 +0200 @@ -998,9 +998,9 @@ } }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2510,13 +2510,13 @@ } }, "node_modules/synckit": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.4.tgz", - "integrity": "sha512-Q/XQKRaJiLiFIBNN+mndW7S/RHxvwzuZS6ZwmRzUBqJBv/5QIKCEwkBC8GBf8EQJKYnaFs0wOZbKTXBPj8L9oQ==", + "version": "0.11.5", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.5.tgz", + "integrity": "sha512-frqvfWyDA5VPVdrWfH24uM6SI/O8NLpVbIIJxb8t/a3YGsp4AW9CYgSKC0OaSEfexnp7Y1pVh2Y6IHO8ggGDmA==", "dev": true, "license": "MIT", "dependencies": { - "@pkgr/core": "^0.2.3", + "@pkgr/core": "^0.2.4", "tslib": "^2.8.1" }, "engines": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1747157239.98c95eac/script/dump-db new/openQA-5.1747282262.9a4e6bb5/script/dump-db --- old/openQA-5.1747157239.98c95eac/script/dump-db 2025-05-13 19:27:19.000000000 +0200 +++ new/openQA-5.1747282262.9a4e6bb5/script/dump-db 2025-05-15 06:11:02.000000000 +0200 @@ -1,6 +1,6 @@ #!/bin/bash set -euo pipefail -DUMP_FOLDER=${DUMP_FOLDER:-"/var/lib/openqa/SQL-DUMPS"} +DUMP_FOLDER=${DUMP_FOLDER:-"/var/lib/openqa/backup"} DAYS_TO_KEEP=${DAYS_TO_KEEP:-"7"} [[ ${1-} = '-h' ]] || [[ ${1-} = '--help' ]] && echo "Dump the openQA postgresql database to ${DUMP_FOLDER} and clean-up backups older than ${DAYS_TO_KEEP} days." && exit diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1747157239.98c95eac/script/openqa-dump-templates new/openQA-5.1747282262.9a4e6bb5/script/openqa-dump-templates --- old/openQA-5.1747157239.98c95eac/script/openqa-dump-templates 2025-05-13 19:27:19.000000000 +0200 +++ new/openQA-5.1747282262.9a4e6bb5/script/openqa-dump-templates 2025-05-15 06:11:02.000000000 +0200 @@ -53,6 +53,12 @@ dump as json +=item B<--convert> + +convert legacy non-YAML job groups to modern YAML + +dump as json + =item B<--help, -h> print help @@ -117,7 +123,7 @@ GetOptions( \%options, "json", "host=s", "apibase=s", "apikey:s", "apisecret:s", "group=s@", "name=s@", "test=s@", "product=s@", "machine=s@", "full", - "help|h", + "convert", "help|h", ) or usage(1); usage(0) if $options{help}; @@ -179,24 +185,41 @@ } if ($tables{'JobGroups'}) { - my $group = (keys %{$options{group}})[0]; - $url->path($options{apibase} . '/job_templates_scheduling/' . ($group // '')); - my $res = $client->get($url)->res; - handle_error($group // 'all groups', $url, $res) unless $res->code && $res->code == 200; - if ($group) { - # This is already the YAML document of a single group - push @{$result{JobGroups}}, {group_name => $group, template => $res->json}; + my $group = defined $options{group} ? (keys %{$options{group}})[0] // '' : ''; + if ($options{convert}) { + # use job_templates_scheduling, which converts if no YAML + # string already exists + $url->path($options{apibase} . '/job_templates_scheduling/' . $group); + my $res = $client->get($url)->res; + handle_error($group || 'all groups', $url, $res) unless $res->code && $res->code == 200; + if ($group) { + # This is already the YAML document of a single group + push @{$result{JobGroups}}, {group_name => $group, template => $res->json}; + } + else { + my $yaml = $res->json; + foreach my $group (sort keys %$yaml) { + push @{$result{JobGroups}}, {group_name => $group, template => $yaml->{$group}}; + } + } } else { - my $yaml = $res->json; - foreach my $group (sort keys %$yaml) { - push @{$result{JobGroups}}, {group_name => $group, template => $yaml->{$group}}; + # use job_groups and store any existing template strings + $url->path($options{apibase} . '/job_groups/'); + my $res = $client->get($url)->res; + handle_error($group || 'all groups', $url, $res) unless $res->code && $res->code == 200; + my $jgroups = $res->json; + for my $jgdata (@$jgroups) { + next unless ($jgdata->{template}); + next if ($group && $jgdata->{name} ne $group); + push @{$result{JobGroups}}, {group_name => $jgdata->{name}, template => $jgdata->{template}}; } } } for my $table (qw(Machines TestSuites Products JobTemplates)) { next unless $tables{$table}; + next if ($table eq "JobTemplates" && $options{convert}); $url->path($options{apibase} . '/' . decamelize($table)); my $res = $client->get($url)->res; handle_error($table, $url, $res) unless $res->code && $res->code == 200; @@ -210,6 +233,8 @@ next if $options{group} && !$options{group}->{$r->{group_name}}; $options{test}->{$r->{test_suite}->{name}} = 1; $options{machine}->{$r->{machine}->{name}} = 1; + # note: $options{product} may not be defined yet, this relies + # on perl autovivification $options{product}->{product_key($r->{product})} = 1; } } @@ -217,7 +242,21 @@ sub handle_row ($table, $r) { if ($table eq 'JobTemplates') { return undef if $options{group} && $r->{group_name} && !$options{group}->{$r->{group_name}}; - return undef unless $options{product}->{product_key($r->{product})}; + return undef if $options{product} && !$options{product}->{product_key($r->{product})}; + # skip if we have YAML data for this group. mixing YAML and + # non-YAML template data for a group is not a good idea + return undef + if $r->{group_name} && $result{JobGroups} && grep { $r->{group_name} eq $_->{group_name} } + @{$result{JobGroups}}; + # see https://github.com/os-autoinst/openQA/pull/2071#issuecomment-2867855025 + if ($r->{settings} || $r->{description}) { + print STDERR + "Settings and/or description unexpectedly present in job template from legacy (non-YAML) job group " + . $r->{group_name} . "!\n"; + print STDERR "Loading the dumped data would lose the settings.\n"; + print STDERR "Dump with --convert to convert to YAML format and preserve settings.\n"; + exit(1); + } } return undef if $table eq 'TestSuites' && $options{test} && $r->{name} && !$options{test}->{$r->{name}}; return undef if $table eq 'Machines' && $options{machine} && $r->{name} && !$options{machine}->{$r->{name}}; @@ -231,8 +270,20 @@ return $r; } +sub jt_key ($jt) { + my $prod = $jt->{product}; + return join('', + $jt->{group_name}, + $jt->{test_suite}->{name}, + $jt->{machine}->{name}, + $prod->{arch}, $prod->{distri}, $prod->{flavor}, $prod->{version}); +} + sub handle_table ($table) { $result{$table} = [grep { defined } map { handle_row($table, $_) } @{$result{$table}}]; + if ($table eq 'JobTemplates') { + $result{$table} = [sort { jt_key($a) cmp jt_key($b) } @{$result{$table}}]; + } } handle_table($_) for keys %result; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1747157239.98c95eac/script/openqa-load-templates new/openQA-5.1747282262.9a4e6bb5/script/openqa-load-templates --- old/openQA-5.1747157239.98c95eac/script/openqa-load-templates 2025-05-13 19:27:19.000000000 +0200 +++ new/openQA-5.1747282262.9a4e6bb5/script/openqa-load-templates 2025-05-15 06:11:02.000000000 +0200 @@ -96,6 +96,9 @@ my $url = OpenQA::Client::url_from_host($options{host}); my $client = OpenQA::Client->new(apikey => $options{'apikey'}, apisecret => $options{'apisecret'}, api => $url->host); my @tables = (qw(Machines TestSuites Products JobTemplates JobGroups)); +# clean JobGroups first so we don't hit +# "must be updated through the YAML template" in the other tables +my @cleantables = (qw(JobGroups Machines TestSuites Products JobTemplates)); sub print_error ($res) { if (my $err = $res->error) { @@ -111,34 +114,33 @@ die "unknown error code - host $url->{host} unreachable?"; } +sub post_yaml_templates ($group_name, $template) { + # Post the job template YAML + my $job_templates_url = $url->clone->path($options{apibase} . '/job_templates_scheduling'); + return $client->post( + $job_templates_url, + form => { + name => $group_name, + template => $template, + schema => 'JobTemplates-01.yaml' + })->res; +} + sub post_entry ($table, $entry) { my %param; if ($table eq 'JobGroups') { - # Create the group first + # Try to create the group first my $job_groups_url = $url->clone->path($options{apibase} . '/job_groups'); - my $existing_group_res = $client->get($job_groups_url, form => {name => $entry->{group_name}})->res; - print_error $existing_group_res unless $existing_group_res->is_success; - my $group_exists = 0; - for my $group (@{$existing_group_res->json}) { - next unless $group->{name} eq $entry->{group_name}; - $group_exists = 1 - if $options{update} - or die "Job group '$entry->{group_name}' already exists. Use --update to modify existing entries."; - } - unless ($group_exists) { - my $create_res = $client->post($job_groups_url, form => {name => $entry->{group_name}})->res; - print_error($create_res) unless $create_res->is_success; - } + my $create_res = $client->post($job_groups_url, form => {name => $entry->{group_name}})->res; + # this is what we get from the API if the group exists + my $exists = ($create_res->code == 500 && $create_res->json->{already_exists}); + # return 0 (indicating no change) unless --clean or --update passed + return 0 if ($exists && !$options{update} && !$options{clean}); + print_error($create_res) unless ($create_res->is_success || $exists); # Post the job template YAML - my $job_templates_url = $url->clone->path($options{apibase} . '/job_templates_scheduling'); - my $yaml_res = $client->post( - $job_templates_url, - form => { - name => $entry->{group_name}, - template => $entry->{template}, - schema => 'JobTemplates-01.yaml' - })->res; + + my $yaml_res = post_yaml_templates($entry->{group_name}, $entry->{template}); print_error($yaml_res) unless $yaml_res->is_success; return 1; } @@ -204,27 +206,38 @@ } } - my $res = $client->post($table_url, json => \%param)->res; + my $type = $table eq 'JobTemplates' ? 'form' : 'json'; + my $res = $client->post($table_url, $type => \%param)->res; print_error $res unless $res->is_success; return 1; } if ($options{'clean'}) { - for my $table (@tables) { - # we can't clean job groups as they're not deletable unless empty - next if ($table eq "JobGroups"); + for my $table (@cleantables) { my $table_url = $url->clone->path($options{apibase} . '/' . decamelize($table)); my $res = $client->get($table_url)->res; if ($res->is_success) { my $result = $res->json; - for my $i (0 .. $#{$result->{$table}}) { - my $id = $result->{$table}->[$i]->{id}; - my $table_url_id = $url->clone->path($options{apibase} . '/' . decamelize($table) . "/$id"); - $res = $client->delete($table_url_id)->res; - last unless $res->is_success; + # we can't clean job groups as they're not deletable + # unless empty, but we *can* replace existing YAML + # template strings with 'empty' ones + if ($table eq "JobGroups") { + # unlike all other tables, is not a hash + for my $jg (@$result) { + next unless ($jg->{template}); + $res = post_yaml_templates($jg->{name}, "scenarios: {}\nproducts: {}\n"); + last unless $res->is_success; + } + } + else { + for my $i (0 .. $#{$result->{$table}}) { + my $id = $result->{$table}->[$i]->{id}; + my $table_url_id = $url->clone->path($options{apibase} . '/' . decamelize($table) . "/$id"); + $res = $client->delete($table_url_id)->res; + last unless $res->is_success; + } } } - print_error $res unless $res->is_success; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1747157239.98c95eac/systemd/openqa-dump-db.service new/openQA-5.1747282262.9a4e6bb5/systemd/openqa-dump-db.service --- old/openQA-5.1747157239.98c95eac/systemd/openqa-dump-db.service 1970-01-01 01:00:00.000000000 +0100 +++ new/openQA-5.1747282262.9a4e6bb5/systemd/openqa-dump-db.service 2025-05-15 06:11:02.000000000 +0200 @@ -0,0 +1,8 @@ +[Unit] +Description=openQA database dump and cleanup task +ConditionPathIsReadWrite=/var/lib/openqa/backup + +[Service] +Type=exec +User=postgres +ExecStart=/usr/share/openqa/script/dump-db diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1747157239.98c95eac/systemd/openqa-dump-db.timer new/openQA-5.1747282262.9a4e6bb5/systemd/openqa-dump-db.timer --- old/openQA-5.1747157239.98c95eac/systemd/openqa-dump-db.timer 1970-01-01 01:00:00.000000000 +0100 +++ new/openQA-5.1747282262.9a4e6bb5/systemd/openqa-dump-db.timer 2025-05-15 06:11:02.000000000 +0200 @@ -0,0 +1,9 @@ +[Unit] +Description=Daily openQA database dump and cleanup task + +[Timer] +OnCalendar=23:40 +Persistent=true + +[Install] +WantedBy=timers.target diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1747157239.98c95eac/t/01-compile-check-all.t new/openQA-5.1747282262.9a4e6bb5/t/01-compile-check-all.t --- old/openQA-5.1747157239.98c95eac/t/01-compile-check-all.t 2025-05-13 19:27:19.000000000 +0200 +++ new/openQA-5.1747282262.9a4e6bb5/t/01-compile-check-all.t 2025-05-15 06:11:02.000000000 +0200 @@ -22,6 +22,7 @@ 't/data/openqa/share/tests/opensuse/tests/installation/installer_timezone.pm', # Skip data files which are supposed to resemble generated output which has no 'use' statements 't/data/40-templates.pl', + 't/data/40-templates-jgs.pl', 't/data/40-templates-more.pl', 't/data/openqa-trigger-from-obs/Proj2::appliances/.api_package', 't/data/openqa-trigger-from-obs/Proj2::appliances/.dirty_status', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1747157239.98c95eac/t/10-jobs.t new/openQA-5.1747282262.9a4e6bb5/t/10-jobs.t --- old/openQA-5.1747157239.98c95eac/t/10-jobs.t 2025-05-13 19:27:19.000000000 +0200 +++ new/openQA-5.1747282262.9a4e6bb5/t/10-jobs.t 2025-05-15 06:11:02.000000000 +0200 @@ -489,7 +489,7 @@ ok($inv = $job->investigate(git_limit => 23), 'job investigation ok with test changes'); my $actual_lines = split(/\n/, $inv->{test_log}); my $expected_lines = 7; - is($actual_lines, $expected_lines, 'test_log have correct number of lines'); + is($actual_lines, $expected_lines, 'test_log has the correct number of lines'); like($inv->{test_log}, qr/^.*file changed/m, 'git log with test changes'); is $got_limit, 23, 'git_limit was correctly passed'; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1747157239.98c95eac/t/40-script_load_dump_templates.t new/openQA-5.1747282262.9a4e6bb5/t/40-script_load_dump_templates.t --- old/openQA-5.1747157239.98c95eac/t/40-script_load_dump_templates.t 2025-05-13 19:27:19.000000000 +0200 +++ new/openQA-5.1747282262.9a4e6bb5/t/40-script_load_dump_templates.t 2025-05-15 06:11:02.000000000 +0200 @@ -6,6 +6,7 @@ use FindBin; use lib "$FindBin::Bin/lib", "$FindBin::Bin/../external/os-autoinst-common/lib"; use File::Temp qw(tempfile); +use Mojo::Base -signatures; use Mojo::File qw(path curfile); require OpenQA::Test::Database; use OpenQA::Test::Utils; @@ -33,6 +34,11 @@ sub decode { Cpanel::JSON::XS->new->relaxed->decode(path(shift)->slurp); } +sub check_property ($schema, $table, $property, $values) { + my @gotprops = sort map { $_->$property } $schema->resultset($table)->all; + is_deeply(\@gotprops, $values, "$property entries in $table as expected") or always_explain \@gotprops; +} + test_once '--help', qr/Usage:/, 'help text shown', 0, 'openqa-load-templates with no arguments shows usage'; test_once '--host', qr/Option host requires an argument/, 'host argument error shown', 1, 'required arguments missing'; @@ -58,22 +64,21 @@ $apisecret = 'EXCALIBUR'; my $base_args = "--host $host --apikey $apikey --apisecret $apisecret"; $args = "$base_args $filename"; -my $expected = qr/JobGroups.+=> \{ added => 1, of => 1 \}/; +my $expected + = qr/JobGroups +=> \{ added => 1, of => 1 \},\n +JobTemplates +=> \{ added => 0, of => 0 \},\n +Machines +=> \{ added => 1, of => 1 \},\n +Products +=> \{ added => 1, of => 1 \},\n +TestSuites +=> \{ added => 1, of => 1 \}/; +my $expectednochange + = qr/JobGroups +=> \{ added => 0, of => 1 \},\n +JobTemplates +=> \{ added => 0, of => 0 \},\n +Machines +=> \{ added => 0, of => 1 \},\n +Products +=> \{ added => 0, of => 1 \},\n +TestSuites +=> \{ added => 0, of => 1 \}/; test_once $args, $expected, 'Admin may load templates', 0, 'successfully loaded templates'; -test_once $args, qr/Use --update to modify/, 'Duplicate job group', 255, 'failed on duplicate job group'; +test_once $args, $expectednochange, 'Reload does not modify without --update', 0, 'succeeded without change'; $args = "$base_args --update $filename"; -test_once $args, $expected, 'Update with existing job group', 0, 'updated template with existing job group'; +test_once $args, $expected, 'Reload with --update modifies', 0, 'updated template with existing job group'; subtest 'test changing existing entries' => sub { - # delete job group so that we can load the template again without running into duplicate job group error my $t = client(Test::Mojo->new(), apikey => $apikey, apisecret => $apisecret); - $t->get_ok("http://$host/api/v1/job_groups")->status_is(200); - my $jobgroup_id = (grep { $_->{name} eq 'openSUSE Leap 42.3 Updates' } @{$t->tx->res->json})[0]->{id}; - $t->delete_ok("http://$host/api/v1/job_groups/$jobgroup_id")->status_is(200); - $t->get_ok("http://$host/api/v1/test_suites?name=uefi")->status_is(200); - my $test_suite_id = $t->tx->res->json->{TestSuites}->[0]->{id}; # overwrite testsuite settings + $t->get_ok("http://$host/api/v1/test_suites?name=uefi")->status_is(200); + my $test_suite_id = $t->tx->res->json->{TestSuites}->[0]->{id}; $t->put_ok("http://$host/api/v1/test_suites/$test_suite_id", json => {name => "uefi", settings => {UEFI => '42'}}) ->status_is(200); @@ -91,6 +96,40 @@ '1', 'value changed back during update'); }; +# the test fixtures that we loaded contain: +# 3 machines '32bit', '64bit', 'Laptop_64' +# job group 1001 'opensuse' (legacy) +# job group 1002 'opensuse test' (legacy) +# 7 test suites textmode, kde, RAID0, client1, server,client2, advanced_kde +# 3 products, one with 10 job templates (all in 'opensuse' group), two with none +# 40-templates.pl which we loaded above contains: +# 1 machine '128bit' +# 1 test suite 'uefi' which occurs in no product / job template +# 1 job group 'openSUSE Leap 42.3 Updates' (modern, empty templates string) +# 1 product opensuse-42.2-DVD-x86_64 +# 0 job templates + +# the 'opensuse' legacy group is unrealistic, as it contains an +# advanced_kde job template with settings and a description. This is +# not possible to achieve via the API (only templates produced from +# a YAML job group can have settings/description). Test that we reject +# dumping such a group unless --convert is passed: +$expected = qr,Settings and/or description unexpectedly present.*group opensuse,; +dump_templates $base_args, $expected, 'dump_templates fails on legacy group with settings', 1, + 'dump_templates handles error'; +$expected = qr/JobGroups\s*=> \[.*group_name\s*=> "opensuse"/s; +$args = "$base_args --convert"; +dump_templates $args, $expected, 'dump_templates with --convert', 0, 'dump_templates success with --convert'; +# Also test with --group, for full code coverage +$args = "$base_args --convert --group opensuse"; +dump_templates $args, $expected, 'dump_templates with --convert --group', 0, + 'dump_templates success with --convert --group'; + +# now wipe the unrealistic settings and description, as following test +# will correctly fail if they are present +$schema->resultset('JobTemplateSettings')->delete; +$schema->resultset('JobTemplates')->update({description => ''}); + my $fh; my $tempfilename; ($fh, $tempfilename) = tempfile(UNLINK => 1, SUFFIX => '.json'); @@ -100,39 +139,117 @@ # Clear the data in relevant tables $schema->resultset($_)->delete for qw(Machines TestSuites Products JobTemplates JobGroups); $args = "$base_args $tempfilename"; -$expected = qr/JobGroups.+=> \{ added => 3, of => 3 \}/; +# we load the modern job group as a JobGroup +# legacy group 'opensuse' will be loaded implicitly via its JobTemplates +# legacy group 'opensuse test' disappears at this point as it has no templates +$expected + = qr/JobGroups +=> \{ added => 1, of => 1 \},\n +JobTemplates +=> \{ added => 10, of => 10 \},\n +Machines +=> \{ added => 4, of => 4 \},\n +Products +=> \{ added => 4, of => 4 \},\n +TestSuites +=> \{ added => 8, of => 8 \}/; test_once $args, $expected, 're-imported fixtures'; my ($rh, $reference) = tempfile(UNLINK => 1, SUFFIX => '.json'); $args = "$base_args --json > $reference"; $expected = qr/^$/; dump_templates $args, $expected, 're-dumped fixtures'; -is_deeply decode($tempfilename), decode($reference), 'both dumps match'; +eq_or_diff_text decode($tempfilename), decode($reference), 'both dumps match'; +# check we have at least vaguely the stuff we intend to have +check_property($schema, 'Machines', 'name', ['128bit', '32bit', '64bit', 'Laptop_64']); +check_property($schema, 'JobGroups', 'name', ['openSUSE Leap 42.3 Updates', 'opensuse']); +check_property($schema, 'JobTemplates', 'prio', [40, 40, 40, 40, 40, 40, 40, 40, 40, 40]); +check_property($schema, 'Products', 'arch', ['i586', 'ppc64', 'x86_64', 'x86_64']); +check_property($schema, 'TestSuites', 'name', + ['RAID0', 'advanced_kde', 'client1', 'client2', 'kde', 'server', 'textmode', 'uefi']); +my $modjg = $schema->resultset('JobGroups')->find({name => 'openSUSE Leap 42.3 Updates'}); +ok $modjg->template, 'modern group template reloaded'; +my $legjg = $schema->resultset('JobGroups')->find({name => 'opensuse'}); +ok !$legjg->template, 'legacy group has no template'; subtest 'dump_templates tests' => sub { $args = $base_args; dump_templates "$args Products42", qr/Invalid table.*42/, 'Error on non-existant table', 1, 'table error'; - $args .= " --test uefi --machine 32bit --group opensuse --product bar --full JobTemplates"; + $args .= " --test uefi --machine 128bit --group opensuse --product bar --full JobTemplates"; $expected = qr/JobTemplates\s*=> \[.*group_name\s*=> "opensuse"/s; dump_templates $args, $expected, 'dump_templates with options', 0, 'dump_templates success with options'; - $args = "$base_args --test uefi --machine 32bit --group \"openSUSE Leap 42\" --product bar --full JobTemplates"; + # this test intends to hit job_templates_scheduling/openSUSE Leap 42 + # and find it doesn't exist; we need --convert to use that endpoint + $args = "$base_args --convert --group \"openSUSE Leap 42\""; $expected = qr/ERROR requesting.*404 - Not Found/; dump_templates $args, $expected, 'dump_templates fails on wrong group', 1, 'dump_templates handles error'; }; +# now dump with --convert and reload, which will convert 'opensuse' to a YAML group +$args = "$base_args --convert --json > $tempfilename"; +$expected = qr/^$/; +dump_templates $args, $expected, 'dumped fixtures'; +# clear data again +$schema->resultset($_)->delete for qw(Machines TestSuites Products JobTemplates JobGroups); +$args = "$base_args $tempfilename"; +$expected + = qr/JobGroups +=> \{ added => 2, of => 2 \},\n +Machines +=> \{ added => 4, of => 4 \},\n +Products +=> \{ added => 4, of => 4 \},\n +TestSuites +=> \{ added => 8, of => 8 \}/; +test_once $args, $expected, 're-imported fixtures'; +$legjg = $schema->resultset('JobGroups')->find({name => 'opensuse'}); +ok $legjg->template, 'formerly-legacy group now has template'; +# the right number of job templates showed up, with priorities +check_property($schema, 'JobTemplates', 'prio', [40, 40, 40, 40, 40, 40, 40, 40, 40, 40]); + # Clear the data in relevant tables again $schema->resultset($_)->delete for qw(Machines TestSuites Products JobTemplates JobGroups); # load the templates file with 2 machines $args = "--host $host --apikey $apikey --apisecret $apisecret $morefilename"; $expected = qr/Machines.+=> \{ added => 2, of => 2 \}/; test_once $args, $expected, 'imported MOAR fixtures'; -# wipe jobgroups manually as --clean explicitly skips it -$schema->resultset("JobGroups")->delete; # now load the templates file with only 1 machine, with --clean $args = "--host $host --apikey $apikey --apisecret $apisecret --clean $filename"; $expected = qr/Machines.+=> \{ added => 1, of => 1 \}/; test_once $args, $expected, 'imported original fixtures'; is $schema->resultset('Machines')->count, 1, "only one machine is loaded"; my $machine = $schema->resultset('Machines')->first; -is $machine->name, "32bit", "correct machine is loaded"; +is $machine->name, "128bit", "correct machine is loaded"; + +# Clear the data in relevant tables again +$schema->resultset($_)->delete for qw(Machines TestSuites Products JobTemplates JobGroups); +# load a template file with YAML job group settings and description +$args = "$base_args t/data/40-templates-jgs.pl"; +$expected = qr/JobGroups +=> \{ added => 1, of => 1 \}/; +test_once $args, $expected, 'imported YAML job groups'; +# check we got the expected job template with settings and description +my $found = 0; +for my $jt ($schema->resultset('JobTemplates')->all) { + next unless $jt->test_suite->name eq 'advanced_kde'; + $found++; + $expected = {DESKTOP => 'advanced_kde', ADVANCED => '1'}; + eq_or_diff $jt->settings_hash, $expected, 'advanced_kde job template has expected settings'; + is $jt->description, 'such advanced very test', 'job template has expected description'; +} +is $found, 1, 'exactly one advanced_kde job template was found'; + +# now let's test --update with YAML groups +# clear the templates, but leave the job group in existence +$schema->resultset($_)->delete for qw(Machines TestSuites Products JobTemplates); +$schema->resultset('JobGroups')->update({template => ''}); +# test reload without --clean or --update does nothing as the group exists +$expected = qr/JobGroups +=> \{ added => 0, of => 1 \}/; +test_once $args, $expected, 're-import without --clean or --update does nothing'; +# test with --update and 'empty' state +$args = "$base_args --update t/data/40-templates-jgs.pl"; +$expected = qr/JobGroups +=> \{ added => 1, of => 1 \}/; +test_once $args, $expected, 're-import with --update works'; +is $schema->resultset('JobTemplates')->count, 3, 'job templates were loaded'; + +# to test YAML --clean, let's rename the group from 40-templates-jgs +# to 'fedora' and add a legacy group +$schema->resultset('JobGroups')->update({name => 'fedora'}); +$schema->resultset('JobGroups')->create({name => 'legacy', description => 'legacy group'}); +$args = "$base_args --clean t/data/40-templates-jgs.pl"; +$expected = qr/JobGroups +=> \{ added => 1, of => 1 \}/; +test_once $args, $expected, 're-import with --clean works'; +is $schema->resultset('JobTemplates')->count, 3, 'job templates were loaded'; +is $schema->resultset('JobGroups')->count, 3, 'we now have three job groups'; +my $fedora = $schema->resultset('JobGroups')->find({name => 'fedora'}); +my $opensuse = $schema->resultset('JobGroups')->find({name => 'opensuse'}); +my $legacy = $schema->resultset('JobGroups')->find({name => 'legacy'}); +is $fedora->template, "scenarios: {}\nproducts: {}\n", 'fedora group template emptied'; +is $legacy->template, undef, 'legacy group template still undefined'; +ok $opensuse->template, 'opensuse group has template'; +# make sure all job templates are for opensuse group not fedora group +check_property($schema, 'JobTemplates', 'group_id', [$opensuse->id, $opensuse->id, $opensuse->id]); done_testing; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1747157239.98c95eac/t/api/01-workers.t new/openQA-5.1747282262.9a4e6bb5/t/api/01-workers.t --- old/openQA-5.1747157239.98c95eac/t/api/01-workers.t 2025-05-13 19:27:19.000000000 +0200 +++ new/openQA-5.1747282262.9a4e6bb5/t/api/01-workers.t 2025-05-15 06:11:02.000000000 +0200 @@ -6,8 +6,10 @@ use FindBin; use lib "$FindBin::Bin/../lib", "$FindBin::Bin/../../external/os-autoinst-common/lib"; +use Mojo::Base -signatures; use Test::Mojo; use Test::Warnings qw(:all :report_warnings); +use Test::Output qw(combined_like); use Mojo::URL; use OpenQA::Test::TimeLimit '10'; use OpenQA::Test::Case; @@ -146,17 +148,65 @@ is($jobs->find($running_job_id)->state, RUNNING, 'assigned job still running'); }; - subtest 'previous job incompleted when worker doing something else' => sub { - delete $registration_params{job_id}; - $t->post_ok('/api/v1/workers', form => \%registration_params) - ->status_is(200, 'register existing worker passing no job ID') - ->json_is('/id' => $worker_id, 'worker ID returned'); - return always_explain $t->tx->res->json unless $t->success; + my $expected_breakage = ''; + my $expected_warning = qr//; + my $test_registration = sub { + $schema->txn_begin; + combined_like sub { $t->post_ok('/api/v1/workers', form => \%registration_params) }, + $expected_warning, 'expected warning logged'; + $t->status_is(200, 'register existing worker passing no job ID'); + $t->json_is('/id' => $worker_id, 'worker ID returned'); + always_explain $t->tx->res->json unless $t->success; + return $schema->txn_rollback if $expected_breakage eq 'worker'; + is $workers->find($worker_id)->job_id, undef, 'worker has no longer an assigned job'; + return $schema->txn_rollback if $expected_breakage eq 'job'; my $incomplete_job = $jobs->find($running_job_id); - is($incomplete_job->state, DONE, 'assigned job set to done'); - ok($incomplete_job->result eq INCOMPLETE || $incomplete_job->result eq PARALLEL_RESTARTED, - 'assigned job considered incomplete or parallel restarted') + is $incomplete_job->state, DONE, 'assigned job set to done'; + ok $incomplete_job->result eq INCOMPLETE || $incomplete_job->result eq PARALLEL_RESTARTED, + 'assigned job considered incomplete or parallel restarted' or always_explain 'actual job result: ' . $incomplete_job->result; + $schema->txn_rollback; + }; + + subtest 'previous job incompleted when worker doing something else' => sub { + delete $registration_params{job_id}; + + subtest 'no errors occurred' => $test_registration; + + # test the worst case where workers will end up stuck with a job + # note: This is not supposed to happen in production as errors are now supposed to be caught + # earlier by the cases tested in further subtests. However, this warning turned out to + # be useful when investigating problems so we should keep it and have this test. + my $mock = Test::MockModule->new('OpenQA::WebAPI::Controller::API::V1::Worker'); + $mock->redefine(_incomplete_previous_job => sub { die 'foo1' }); + $expected_breakage = 'worker'; + $expected_warning = qr/Unable to incomplete.*reschedule.*abandoned by worker 2: foo1/; + subtest 'incompleting previous job fails' => $test_registration; + + # test that everything else works as expected despite failing duplication + $mock = Test::MockModule->new('OpenQA::Schema::Result::Jobs'); + $mock->redefine(auto_duplicate => sub { die 'foo2' }); + $expected_breakage = ''; + $expected_warning = qr/Unable to duplicate job.*abandoned by worker remotehost:1: foo2/; + subtest 'failure when duplicating job does not prevent anything else' => $test_registration; + + # test that when we cannot set the job to done the worker is at least freed from its job + # note: It is very important to free the worker from its job as it would otherwise be stuck + # forever. The jobs seem to be mostly cancelled by obsoletion in practices anyway and + # can also be cancelled manually by users if needed. + $mock->unmock('auto_duplicate'); + $mock->redefine(done => sub { die 'foo3' }); + $expected_breakage = 'job'; + $expected_warning = qr/Unable to incomplete job.*abandoned by worker remotehost:1: foo3/; + subtest 'worker still freed from its job, even if job cannot be set done' => $test_registration; + + # test that failures when reading results (e.g. for carry over) are not preventing anything else to happen + $mock = Test::MockModule->new('OpenQA::Schema::Result::JobModules'); + $jobs->find($running_job_id)->modules->create({name => 'foo', script => 'bar', category => 'testsuite'}); + $mock->redefine(results => sub ($self, %options) { die 'foo4' }); + $expected_breakage = ''; + $expected_warning = qr/Unable to carry-over bugrefs of job.*: foo4/; + subtest 'failure when reading job module results does not prevent anything else' => $test_registration; }; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1747157239.98c95eac/t/data/40-templates-jgs.pl new/openQA-5.1747282262.9a4e6bb5/t/data/40-templates-jgs.pl --- old/openQA-5.1747157239.98c95eac/t/data/40-templates-jgs.pl 1970-01-01 01:00:00.000000000 +0100 +++ new/openQA-5.1747282262.9a4e6bb5/t/data/40-templates-jgs.pl 2025-05-15 06:11:02.000000000 +0200 @@ -0,0 +1,46 @@ +{ + JobGroups => [ + { + group_name => "opensuse", + template => +"defaults:\n i586:\n machine: 64bit\n priority: 50\nproducts:\n opensuse-13.1-DVD-i586:\n distri: opensuse\n flavor: DVD\n version: \'13.1\'\nscenarios:\n i586:\n opensuse-13.1-DVD-i586:\n - textmode:\n machine: 32bit\n priority: 40\n - textmode:\n machine: 64bit\n priority: 40\n - advanced_kde:\n description: such advanced very test\n priority: 40\n settings:\n ADVANCED: \'1\'\n DESKTOP: advanced_kde", + }, + ], + JobTemplates => [], + Machines => [ + { + backend => "qemu", + name => "32bit", + settings => [], + }, + { + backend => "qemu", + name => "64bit", + settings => [], + }, + ], + Products => [ + { + arch => "i586", + distri => "opensuse", + flavor => "DVD", + settings => [], + version => 13.1, + }, + ], + TestSuites => [ + { + name => "textmode", + settings => [{key => "DESKTOP", value => "textmode"}, {key => "VIDEOMODE", value => "text"},], + }, + { + name => "advanced_kde", + description => 'See kde for simple test', + settings => [ + {key => "DESKTOP", value => "kde"}, + {key => "START_AFTER_TEST", value => "kde,textmode"}, + {key => "PUBLISH_HDD_1", value => '%DISTRI%-%VERSION%-%ARCH%-%DESKTOP%-%QEMUCPU%.qcow2'} + ], + }, + ], +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1747157239.98c95eac/t/data/40-templates-more.pl new/openQA-5.1747282262.9a4e6bb5/t/data/40-templates-more.pl --- old/openQA-5.1747157239.98c95eac/t/data/40-templates-more.pl 2025-05-13 19:27:19.000000000 +0200 +++ new/openQA-5.1747282262.9a4e6bb5/t/data/40-templates-more.pl 2025-05-15 06:11:02.000000000 +0200 @@ -9,12 +9,12 @@ Machines => [ { backend => "qemu", - name => "32bit", + name => "128bit", settings => [], }, { backend => "qemu", - name => "64bit", + name => "256bit", settings => [], }, ], diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1747157239.98c95eac/t/data/40-templates.json new/openQA-5.1747282262.9a4e6bb5/t/data/40-templates.json --- old/openQA-5.1747157239.98c95eac/t/data/40-templates.json 2025-05-13 19:27:19.000000000 +0200 +++ new/openQA-5.1747282262.9a4e6bb5/t/data/40-templates.json 1970-01-01 01:00:00.000000000 +0100 @@ -1,35 +0,0 @@ -{ - "Machines" : [ - { - "name" : "32bit", - "backend" : "qemu" - } - ], - "JobGroups" : [ - { - "group_name" : "openSUSE Leap 42.3 Updates" - } - ], - "Products" : [ - { - "arch" : "x86_64", - "version" : "42.2", - "flavor" : "DVD", - "distri" : "opensuse" - } - ], - "JobTemplates" : [ - ], - "TestSuites" : [ - { - "description" : "Maintainer: averagea\n\nThat's some test right there", - "name" : "uefi", - "settings" : [ - { - "key" : "DESKTOP", - "value" : "gnome" - } - ] - } - ] -} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1747157239.98c95eac/t/data/40-templates.pl new/openQA-5.1747282262.9a4e6bb5/t/data/40-templates.pl --- old/openQA-5.1747157239.98c95eac/t/data/40-templates.pl 2025-05-13 19:27:19.000000000 +0200 +++ new/openQA-5.1747282262.9a4e6bb5/t/data/40-templates.pl 2025-05-15 06:11:02.000000000 +0200 @@ -9,7 +9,7 @@ Machines => [ { backend => "qemu", - name => "32bit", + name => "128bit", settings => [], }, ], ++++++ openQA.obsinfo ++++++ --- /var/tmp/diff_new_pack.SYkaFg/_old 2025-05-20 09:34:26.848082377 +0200 +++ /var/tmp/diff_new_pack.SYkaFg/_new 2025-05-20 09:34:26.852082545 +0200 @@ -1,5 +1,5 @@ name: openQA -version: 5.1747157239.98c95eac -mtime: 1747157239 -commit: 98c95eac234957bfee6d467b3e9f630085a83dde +version: 5.1747282262.9a4e6bb5 +mtime: 1747282262 +commit: 9a4e6bb524ff38106d457d394d5492340a32e92f